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