]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - contrib/sunplug/sunplug.cpp
my own uncrustify run
[xonotic/netradiant.git] / contrib / sunplug / sunplug.cpp
1 /*
2    Sunplug plugin for GtkRadiant
3    Copyright (C) 2004 Topsun
4    Thanks to SPoG for help!
5
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.
10
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.
15
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
19  */
20
21 #include "sunplug.h"
22
23 #include "debugging/debugging.h"
24
25 #include "iplugin.h"
26
27 #include "string/string.h"
28 #include "modulesystem/singletonmodule.h"
29
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
33
34 #include "scenelib.h"    // declaration of datastructure of the map
35 #include "qerplugin.h"   // declaration to use other interfaces as a plugin
36
37 #include <gtk/gtk.h>     // to display something with gtk (windows, buttons etc.), the whole package might not be necessary
38
39 void about_plugin_window();
40 void MapCoordinator();
41
42 #ifndef _WIN32
43 // linux itoa implementation
44 char* itoa( int value, char* result, int base ){
45         // check that the base if valid
46         if ( base < 2 || base > 16 ) {
47                 *result = 0;
48                 return result;
49         }
50
51         char* out = result;
52         int quotient = value;
53
54         do
55         {
56                 *out = "0123456789abcdef"[abs( quotient % base )];
57                 ++out;
58
59                 quotient /= base;
60         } while ( quotient );
61
62         // Only apply negative sign for base 10
63         if ( value < 0 && base == 10 ) {
64                 *out++ = '-';
65         }
66
67         std::reverse( result, out );
68
69         *out = 0;
70         return result;
71 }
72 #endif
73
74 typedef struct _mapcoord_setting_packet {
75         GtkSpinButton *spinner1, *spinner2, *spinner3, *spinner4;
76         Entity* worldspawn;
77 } mapcoord_setting_packet;
78
79 static int map_minX, map_maxX, map_minY, map_maxY;
80 static int minX, maxX, minY, maxY;
81 mapcoord_setting_packet msp;
82
83 //  **************************
84 // ** find entities by class **  from radiant/map.cpp
85 //  **************************
86 class EntityFindByClassname : public scene::Graph::Walker
87 {
88 const char* m_name;
89 Entity*& m_entity;
90 public:
91 EntityFindByClassname( const char* name, Entity*& entity ) : m_name( name ), m_entity( entity ){
92         m_entity = 0;
93 }
94 bool pre( const scene::Path& path, scene::Instance& instance ) const {
95         if ( m_entity == 0 ) {
96                 Entity* entity = Node_getEntity( path.top() );
97                 if ( entity != 0
98                          && string_equal( m_name, entity->getKeyValue( "classname" ) ) ) {
99                         m_entity = entity;
100                 }
101         }
102         return true;
103 }
104 };
105
106 Entity* Scene_FindEntityByClass( const char* name ){
107         Entity* entity;
108         GlobalSceneGraph().traverse( EntityFindByClassname( name, entity ) );
109         return entity;
110 }
111
112 //  **************************
113 // ** GTK callback functions **
114 //  **************************
115
116 static gboolean delete_event( GtkWidget *widget, GdkEvent *event, gpointer data ){
117         /* If you return FALSE in the "delete_event" signal handler,
118          * GTK will emit the "destroy" signal. Returning TRUE means
119          * you don't want the window to be destroyed.
120          * This is useful for popping up 'are you sure you want to quit?'
121          * type dialogs. */
122
123         return FALSE;
124 }
125
126 // destroy widget if destroy signal is passed to widget
127 static void destroy( GtkWidget *widget, gpointer data ){
128         gtk_widget_destroy( widget );
129 }
130
131 // function for close button to destroy the toplevel widget
132 static void close_window( GtkWidget *widget, gpointer data ){
133         gtk_widget_destroy( gtk_widget_get_toplevel( widget ) );
134 }
135
136 // callback function to assign the optimal mapcoords to the spinboxes
137 static void input_optimal( GtkWidget *widget, gpointer data ){
138         gtk_spin_button_set_value( msp.spinner1, minX );
139         gtk_spin_button_set_value( msp.spinner2, maxY );
140         gtk_spin_button_set_value( msp.spinner3, maxX );
141         gtk_spin_button_set_value( msp.spinner4, minY );
142 }
143
144 // Spinner return value function
145 gint grab_int_value( GtkSpinButton *a_spinner, gpointer user_data ) {
146         return gtk_spin_button_get_value_as_int( a_spinner );
147 }
148
149 // write the values of the Spinner-Boxes to the worldspawn
150 static void set_coordinates( GtkWidget *widget, gpointer data ){
151         //Str str_min, str_max;
152         char buffer[10], str_min[20], str_max[20];
153
154         itoa( gtk_spin_button_get_value_as_int( msp.spinner1 ), str_min, 10 );
155         itoa( gtk_spin_button_get_value_as_int( msp.spinner2 ), buffer, 10 );
156         strcat( str_min, " " );
157         strcat( str_min, buffer );
158         msp.worldspawn->setKeyValue( "mapcoordsmins", str_min );
159
160         itoa( gtk_spin_button_get_value_as_int( msp.spinner3 ), str_max, 10 );
161         itoa( gtk_spin_button_get_value_as_int( msp.spinner4 ), buffer, 10 );
162         strcat( str_max, " " );
163         strcat( str_max, buffer );
164         UndoableCommand undo( "SunPlug.entitySetMapcoords" );
165         msp.worldspawn->setKeyValue( "mapcoordsmaxs", str_max );
166
167         close_window( widget, NULL );
168 }
169
170 class SunPlugPluginDependencies :
171         public GlobalRadiantModuleRef,  // basic class for all other module refs
172         public GlobalUndoModuleRef,     // used to say radiant that something has changed and to undo that
173         public GlobalSceneGraphModuleRef, // necessary to handle data in the mapfile (change, retrieve data)
174         public GlobalEntityModuleRef    // to access and modify the entities
175 {
176 public:
177 SunPlugPluginDependencies() :
178         GlobalEntityModuleRef( GlobalRadiant().getRequiredGameDescriptionKeyValue( "entities" ) ){ //,
179 }
180 };
181
182 //  *************************
183 // ** standard plugin stuff **
184 //  *************************
185 namespace SunPlug
186 {
187 GtkWindow* main_window;
188 char MenuList[100] = "";
189
190 const char* init( void* hApp, void* pMainWidget ){
191         main_window = GTK_WINDOW( pMainWidget );
192         return "Initializing SunPlug for GTKRadiant";
193 }
194 const char* getName(){
195         return "SunPlug"; // name that is shown in the menue
196 }
197 const char* getCommandList(){
198         const char about[] = "About...";
199         const char etMapCoordinator[] = ";ET-MapCoordinator";
200
201         strcat( MenuList, about );
202         if ( strncmp( GlobalRadiant().getGameName(), "etmain", 6 ) == 0 ) {
203                 strcat( MenuList, etMapCoordinator );
204         }
205         return (const char*)MenuList;
206 }
207 const char* getCommandTitleList(){
208         return "";
209 }
210 void dispatch( const char* command, float* vMin, float* vMax, bool bSingleBrush ){ // message processing
211         if ( string_equal( command, "About..." ) ) {
212                 about_plugin_window();
213         }
214         if ( string_equal( command, "ET-MapCoordinator" ) ) {
215                 MapCoordinator();
216         }
217 }
218 } // namespace
219
220 class SunPlugModule : public TypeSystemRef
221 {
222 _QERPluginTable m_plugin;
223 public:
224 typedef _QERPluginTable Type;
225 STRING_CONSTANT( Name, "SunPlug" );
226
227 SunPlugModule(){
228         m_plugin.m_pfnQERPlug_Init = &SunPlug::init;
229         m_plugin.m_pfnQERPlug_GetName = &SunPlug::getName;
230         m_plugin.m_pfnQERPlug_GetCommandList = &SunPlug::getCommandList;
231         m_plugin.m_pfnQERPlug_GetCommandTitleList = &SunPlug::getCommandTitleList;
232         m_plugin.m_pfnQERPlug_Dispatch = &SunPlug::dispatch;
233 }
234 _QERPluginTable* getTable(){
235         return &m_plugin;
236 }
237 };
238
239 typedef SingletonModule<SunPlugModule, SunPlugPluginDependencies> SingletonSunPlugModule;
240
241 SingletonSunPlugModule g_SunPlugModule;
242
243
244 extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules( ModuleServer& server ){
245         initialiseModule( server );
246
247         g_SunPlugModule.selfRegister();
248 }
249
250 //  ************
251 // ** my stuff **
252 //  ************
253
254 // About dialog
255 void about_plugin_window(){
256         GtkWidget *window, *vbox, *label, *button;
257
258         window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); // create a window
259         gtk_window_set_transient_for( GTK_WINDOW( window ), SunPlug::main_window ); // make the window to stay in front of the main window
260         g_signal_connect( G_OBJECT( window ), "delete_event", G_CALLBACK( delete_event ), NULL ); // connect the delete event
261         g_signal_connect( G_OBJECT( window ), "destroy", G_CALLBACK( destroy ), NULL ); // connect the destroy event for the window
262         gtk_window_set_title( GTK_WINDOW( window ), "About SunPlug" ); // set the title of the window for the window
263         gtk_window_set_resizable( GTK_WINDOW( window ), FALSE ); // don't let the user resize the window
264         gtk_window_set_modal( GTK_WINDOW( window ), TRUE ); // force the user not to do something with the other windows
265         gtk_container_set_border_width( GTK_CONTAINER( window ), 10 ); // set the border of the window
266
267         vbox = gtk_vbox_new( FALSE, 10 ); // create a box to arrange new objects vertically
268         gtk_container_add( GTK_CONTAINER( window ), vbox ); // add the box to the window
269
270         label = gtk_label_new( "SunPlug v1.0 for NetRadiant 1.5\nby Topsun" ); // create a label
271         gtk_label_set_justify( GTK_LABEL( label ), GTK_JUSTIFY_LEFT ); // text align left
272         gtk_box_pack_start( GTK_BOX( vbox ), label, FALSE, FALSE, 2 ); // insert the label in the box
273
274         button = gtk_button_new_with_label( "OK" ); // create a button with text
275         g_signal_connect_swapped( G_OBJECT( button ), "clicked", G_CALLBACK( gtk_widget_destroy ), window ); // connect the click event to close the window
276         gtk_box_pack_start( GTK_BOX( vbox ), button, FALSE, FALSE, 2 ); // insert the button in the box
277
278         gtk_window_set_position( GTK_WINDOW( window ), GTK_WIN_POS_CENTER ); // center the window on screen
279
280         gtk_widget_show_all( window ); // show the window and all subelements
281 }
282
283 // get the current bounding box and return the optimal coordinates
284 void GetOptimalCoordinates( AABB *levelBoundingBox ){
285         int half_width, half_heigth, center_x, center_y;
286
287         half_width = levelBoundingBox->extents.x();
288         half_heigth = levelBoundingBox->extents.y();
289         center_x = levelBoundingBox->origin.x();
290         center_y = levelBoundingBox->origin.y();
291
292         if ( half_width > 175 || half_heigth > 175 ) { // the square must be at least 350x350 units
293                 // the wider side is the indicator for the square
294                 if ( half_width >= half_heigth ) {
295                         minX = center_x - half_width;
296                         maxX = center_x + half_width;
297                         minY = center_y - half_width;
298                         maxY = center_y + half_width;
299                 }
300                 else {
301                         minX = center_x - half_heigth;
302                         maxX = center_x + half_heigth;
303                         minY = center_y - half_heigth;
304                         maxY = center_y + half_heigth;
305                 }
306         }
307         else {
308                 minX = center_x - 175;
309                 maxX = center_x + 175;
310                 minY = center_y - 175;
311                 maxY = center_y + 175;
312         }
313 }
314
315 // MapCoordinator dialog window
316 void MapCoordinator(){
317         GtkWidget *window, *vbox, *table, *label, *spinnerMinX, *spinnerMinY, *spinnerMaxX, *spinnerMaxY, *button;
318         GtkAdjustment *spinner_adj_MinX, *spinner_adj_MinY, *spinner_adj_MaxX, *spinner_adj_MaxY;
319         Entity *theWorldspawn = NULL;
320         const char *buffer;
321         char line[20];
322
323         // in any case we need a window to show the user what to do
324         window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); // create the window
325         gtk_window_set_transient_for( GTK_WINDOW( window ), SunPlug::main_window ); // make the window to stay in front of the main window
326         g_signal_connect( G_OBJECT( window ), "delete_event", G_CALLBACK( delete_event ), NULL ); // connect the delete event for the window
327         g_signal_connect( G_OBJECT( window ), "destroy", G_CALLBACK( destroy ), NULL ); // connect the destroy event for the window
328         gtk_window_set_title( GTK_WINDOW( window ), "ET-MapCoordinator" ); // set the title of the window for the window
329         gtk_window_set_resizable( GTK_WINDOW( window ), FALSE ); // don't let the user resize the window
330         gtk_window_set_modal( GTK_WINDOW( window ), TRUE ); // force the user not to do something with the other windows
331         gtk_container_set_border_width( GTK_CONTAINER( window ), 10 ); // set the border of the window
332
333         vbox = gtk_vbox_new( FALSE, 10 ); // create a box to arrange new objects vertically
334         gtk_container_add( GTK_CONTAINER( window ), vbox ); // add the box to the window
335
336         scene::Path path = makeReference( GlobalSceneGraph().root() ); // get the path to the root element of the graph
337         scene::Instance* instance = GlobalSceneGraph().find( path ); // find the instance to the given path
338         AABB levelBoundingBox = instance->worldAABB(); // get the bounding box of the level
339
340         theWorldspawn = Scene_FindEntityByClass( "worldspawn" ); // find the entity worldspawn
341         if ( theWorldspawn != 0 ) { // need to have a worldspawn otherwise setting a value crashes the radiant
342                 // next two if's: get the current values of the mapcoords
343                 buffer = theWorldspawn->getKeyValue( "mapcoordsmins" ); // upper left corner
344                 if ( strlen( buffer ) > 0 ) {
345                         strncpy( line, buffer, 19 );
346                         map_minX = atoi( strtok( line, " " ) ); // minimum of x value
347                         map_minY = atoi( strtok( NULL, " " ) ); // maximum of y value
348                 }
349                 else {
350                         map_minX = 0;
351                         map_minY = 0;
352                 }
353                 buffer = theWorldspawn->getKeyValue( "mapcoordsmaxs" ); // lower right corner
354                 if ( strlen( buffer ) > 0 ) {
355                         strncpy( line, buffer, 19 );
356                         map_maxX = atoi( strtok( line, " " ) ); // maximum of x value
357                         map_maxY = atoi( strtok( NULL, " " ) ); // minimum of y value
358                 }
359                 else {
360                         map_maxX = 0;
361                         map_maxY = 0;
362                 }
363
364                 globalOutputStream() << "SunPlug: calculating optimal coordinates\n"; // write to console that we are calculating the coordinates
365                 GetOptimalCoordinates( &levelBoundingBox ); // calculate optimal mapcoords with the dimensions of the level bounding box
366                 globalOutputStream() << "SunPlug: adviced mapcoordsmins=" << minX << " " << maxY << "\n"; // console info about mapcoordsmins
367                 globalOutputStream() << "SunPlug: adviced mapcoordsmaxs=" << maxX << " " << minY << "\n"; // console info about mapcoordsmaxs
368
369                 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
370                 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
371                 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
372                 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
373
374                 button = gtk_button_new_with_label( "Get optimal mapcoords" ); // create button with text
375                 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( input_optimal ), NULL ); // connect button with callback function
376                 gtk_box_pack_start( GTK_BOX( vbox ), button, FALSE, FALSE, 2 ); // insert button into vbox
377
378                 gtk_box_pack_start( GTK_BOX( vbox ), gtk_hseparator_new(), FALSE, FALSE, 2 ); // insert separator into vbox
379
380                 table = gtk_table_new( 4, 3, TRUE ); // create table
381                 gtk_table_set_row_spacings( GTK_TABLE( table ), 8 ); // set row spacings
382                 gtk_table_set_col_spacings( GTK_TABLE( table ), 8 ); // set column spacings
383                 gtk_box_pack_start( GTK_BOX( vbox ), table, FALSE, FALSE, 2 ); // insert table into vbox
384
385                 label = gtk_label_new( "x" ); // create label
386                 gtk_label_set_justify( GTK_LABEL( label ), GTK_JUSTIFY_LEFT ); // align text to the left side
387                 gtk_table_attach_defaults( GTK_TABLE( table ), label, 1, 2, 0, 1 ); // insert label into table
388
389                 label = gtk_label_new( "y" ); // create label
390                 gtk_label_set_justify( GTK_LABEL( label ), GTK_JUSTIFY_LEFT ); // align text to the left side
391                 gtk_table_attach_defaults( GTK_TABLE( table ), label, 2, 3, 0, 1 ); // insert label into table
392
393                 label = gtk_label_new( "mapcoordsmins" ); // create label
394                 gtk_label_set_justify( GTK_LABEL( label ), GTK_JUSTIFY_LEFT ); // align text to the left side
395                 gtk_table_attach_defaults( GTK_TABLE( table ), label, 0, 1, 1, 2 ); // insert label into table
396
397                 spinnerMinX = gtk_spin_button_new( spinner_adj_MinX, 1.0, 0 ); // create textbox wiht value spin, value and value range
398                 gtk_table_attach_defaults( GTK_TABLE( table ), spinnerMinX, 1, 2, 1, 2 ); // insert spinbox into table
399
400                 spinnerMinY = gtk_spin_button_new( spinner_adj_MinY, 1.0, 0 ); // create textbox wiht value spin, value and value range
401                 gtk_table_attach_defaults( GTK_TABLE( table ), spinnerMinY, 2, 3, 1, 2 ); // insert spinbox into table
402
403                 label = gtk_label_new( "mapcoordsmaxs" ); // create label
404                 gtk_label_set_justify( GTK_LABEL( label ), GTK_JUSTIFY_LEFT ); // align text to the left side
405                 gtk_table_attach_defaults( GTK_TABLE( table ), label, 0, 1, 2, 3 ); // insert label into table
406
407                 spinnerMaxX = gtk_spin_button_new( spinner_adj_MaxX, 1.0, 0 ); // create textbox wiht value spin, value and value range
408                 gtk_table_attach_defaults( GTK_TABLE( table ), spinnerMaxX, 1, 2, 2, 3 ); // insert spinbox into table
409
410                 spinnerMaxY = gtk_spin_button_new( spinner_adj_MaxY, 1.0, 0 ); // create textbox wiht value spin, value and value range
411                 gtk_table_attach_defaults( GTK_TABLE( table ), spinnerMaxY, 2, 3, 2, 3 ); // insert spinbox into table
412
413                 // put the references to the spinboxes and the worldspawn into the global exchange
414                 msp.spinner1 = GTK_SPIN_BUTTON( spinnerMinX );
415                 msp.spinner2 = GTK_SPIN_BUTTON( spinnerMinY );
416                 msp.spinner3 = GTK_SPIN_BUTTON( spinnerMaxX );
417                 msp.spinner4 = GTK_SPIN_BUTTON( spinnerMaxY );
418                 msp.worldspawn = theWorldspawn;
419
420                 button = gtk_button_new_with_label( "Set" ); // create button with text
421                 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( set_coordinates ), NULL ); // connect button with callback function
422                 gtk_table_attach_defaults( GTK_TABLE( table ), button, 1, 2, 3, 4 ); // insert button into table
423
424                 button = gtk_button_new_with_label( "Cancel" ); // create button with text
425                 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( close_window ), NULL ); // connect button with callback function
426                 gtk_table_attach_defaults( GTK_TABLE( table ), button, 2, 3, 3, 4 ); // insert button into table
427         }
428         else {
429                 globalOutputStream() << "SunPlug: no worldspawn found!\n"; // output error to console
430
431                 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
432                 gtk_label_set_justify( GTK_LABEL( label ), GTK_JUSTIFY_LEFT ); // text align left
433                 gtk_box_pack_start( GTK_BOX( vbox ), label, FALSE, FALSE, 2 ); // insert the label in the box
434
435                 button = gtk_button_new_with_label( "OK" ); // create a button with text
436                 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( close_window ), NULL ); // connect the click event to close the window
437                 gtk_box_pack_start( GTK_BOX( vbox ), button, FALSE, FALSE, 2 ); // insert the button in the box
438         }
439
440         gtk_window_set_position( GTK_WINDOW( window ), GTK_WIN_POS_CENTER ); // center the window
441         gtk_widget_show_all( window ); // show the window and all subelements
442 }