]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - contrib/sunplug/sunplug.cpp
Merge branch 'TimePath/gtk++' into 'master'
[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 ui::Window main_window{ui::null};
188 char MenuList[100] = "";
189
190 const char* init( void* hApp, void* pMainWidget ){
191         main_window = ui::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 *label, *button;
257
258         auto window = ui::Window( ui::window_type::TOP ); // 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         window.connect( "delete_event", G_CALLBACK( delete_event ), NULL ); // connect the delete event
261         window.connect( "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         auto vbox = ui::VBox( FALSE, 10 ); // create a box to arrange new objects vertically
268         window.add(vbox);
269
270         label = ui::Label( "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 = ui::Button( "OK" ); // create a button with text
275         g_signal_connect_swapped( G_OBJECT( button ), "clicked", G_CALLBACK( gtk_widget_destroy ), (void *) 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 *table, *label, *spinnerMinX, *spinnerMinY, *spinnerMaxX, *spinnerMaxY;
318         Entity *theWorldspawn = NULL;
319         const char *buffer;
320         char line[20];
321
322         // in any case we need a window to show the user what to do
323         auto window = ui::Window( ui::window_type::TOP ); // create the window
324         gtk_window_set_transient_for( GTK_WINDOW( window ), SunPlug::main_window ); // make the window to stay in front of the main window
325         window.connect( "delete_event", G_CALLBACK( delete_event ), NULL ); // connect the delete event for the window
326         window.connect( "destroy", G_CALLBACK( destroy ), NULL ); // connect the destroy event for the window
327         gtk_window_set_title( GTK_WINDOW( window ), "ET-MapCoordinator" ); // set the title of the window for the window
328         gtk_window_set_resizable( GTK_WINDOW( window ), FALSE ); // don't let the user resize the window
329         gtk_window_set_modal( GTK_WINDOW( window ), TRUE ); // force the user not to do something with the other windows
330         gtk_container_set_border_width( GTK_CONTAINER( window ), 10 ); // set the border of the window
331
332         auto vbox = ui::VBox( FALSE, 10 ); // create a box to arrange new objects vertically
333         window.add(vbox);
334
335         scene::Path path = makeReference( GlobalSceneGraph().root() ); // get the path to the root element of the graph
336         scene::Instance* instance = GlobalSceneGraph().find( path ); // find the instance to the given path
337         AABB levelBoundingBox = instance->worldAABB(); // get the bounding box of the level
338
339         theWorldspawn = Scene_FindEntityByClass( "worldspawn" ); // find the entity worldspawn
340         if ( theWorldspawn != 0 ) { // need to have a worldspawn otherwise setting a value crashes the radiant
341                 // next two if's: get the current values of the mapcoords
342                 buffer = theWorldspawn->getKeyValue( "mapcoordsmins" ); // upper left corner
343                 if ( strlen( buffer ) > 0 ) {
344                         strncpy( line, buffer, 19 );
345                         map_minX = atoi( strtok( line, " " ) ); // minimum of x value
346                         map_minY = atoi( strtok( NULL, " " ) ); // maximum of y value
347                 }
348                 else {
349                         map_minX = 0;
350                         map_minY = 0;
351                 }
352                 buffer = theWorldspawn->getKeyValue( "mapcoordsmaxs" ); // lower right corner
353                 if ( strlen( buffer ) > 0 ) {
354                         strncpy( line, buffer, 19 );
355                         map_maxX = atoi( strtok( line, " " ) ); // maximum of x value
356                         map_maxY = atoi( strtok( NULL, " " ) ); // minimum of y value
357                 }
358                 else {
359                         map_maxX = 0;
360                         map_maxY = 0;
361                 }
362
363                 globalOutputStream() << "SunPlug: calculating optimal coordinates\n"; // write to console that we are calculating the coordinates
364                 GetOptimalCoordinates( &levelBoundingBox ); // calculate optimal mapcoords with the dimensions of the level bounding box
365                 globalOutputStream() << "SunPlug: adviced mapcoordsmins=" << minX << " " << maxY << "\n"; // console info about mapcoordsmins
366                 globalOutputStream() << "SunPlug: adviced mapcoordsmaxs=" << maxX << " " << minY << "\n"; // console info about mapcoordsmaxs
367
368                 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
369                 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
370                 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
371                 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
372
373                 auto button = ui::Button( "Get optimal mapcoords" ); // create button with text
374                 button.connect( "clicked", G_CALLBACK( input_optimal ), NULL ); // connect button with callback function
375                 gtk_box_pack_start( GTK_BOX( vbox ), button, FALSE, FALSE, 2 ); // insert button into vbox
376
377                 gtk_box_pack_start( GTK_BOX( vbox ), gtk_hseparator_new(), FALSE, FALSE, 2 ); // insert separator into vbox
378
379                 table = ui::Table( 4, 3, TRUE ); // create table
380                 gtk_table_set_row_spacings( GTK_TABLE( table ), 8 ); // set row spacings
381                 gtk_table_set_col_spacings( GTK_TABLE( table ), 8 ); // set column spacings
382                 gtk_box_pack_start( GTK_BOX( vbox ), table, FALSE, FALSE, 2 ); // insert table into vbox
383
384                 label = ui::Label( "x" ); // create label
385                 gtk_label_set_justify( GTK_LABEL( label ), GTK_JUSTIFY_LEFT ); // align text to the left side
386                 gtk_table_attach_defaults( GTK_TABLE( table ), label, 1, 2, 0, 1 ); // insert label into table
387
388                 label = ui::Label( "y" ); // create label
389                 gtk_label_set_justify( GTK_LABEL( label ), GTK_JUSTIFY_LEFT ); // align text to the left side
390                 gtk_table_attach_defaults( GTK_TABLE( table ), label, 2, 3, 0, 1 ); // insert label into table
391
392                 label = ui::Label( "mapcoordsmins" ); // create label
393                 gtk_label_set_justify( GTK_LABEL( label ), GTK_JUSTIFY_LEFT ); // align text to the left side
394                 gtk_table_attach_defaults( GTK_TABLE( table ), label, 0, 1, 1, 2 ); // insert label into table
395
396                 spinnerMinX = ui::SpinButton( spinner_adj_MinX, 1.0, 0 ); // create textbox wiht value spin, value and value range
397                 gtk_table_attach_defaults( GTK_TABLE( table ), spinnerMinX, 1, 2, 1, 2 ); // insert spinbox into table
398
399                 spinnerMinY = ui::SpinButton( spinner_adj_MinY, 1.0, 0 ); // create textbox wiht value spin, value and value range
400                 gtk_table_attach_defaults( GTK_TABLE( table ), spinnerMinY, 2, 3, 1, 2 ); // insert spinbox into table
401
402                 label = ui::Label( "mapcoordsmaxs" ); // create label
403                 gtk_label_set_justify( GTK_LABEL( label ), GTK_JUSTIFY_LEFT ); // align text to the left side
404                 gtk_table_attach_defaults( GTK_TABLE( table ), label, 0, 1, 2, 3 ); // insert label into table
405
406                 spinnerMaxX = ui::SpinButton( spinner_adj_MaxX, 1.0, 0 ); // create textbox wiht value spin, value and value range
407                 gtk_table_attach_defaults( GTK_TABLE( table ), spinnerMaxX, 1, 2, 2, 3 ); // insert spinbox into table
408
409                 spinnerMaxY = ui::SpinButton( spinner_adj_MaxY, 1.0, 0 ); // create textbox wiht value spin, value and value range
410                 gtk_table_attach_defaults( GTK_TABLE( table ), spinnerMaxY, 2, 3, 2, 3 ); // insert spinbox into table
411
412                 // put the references to the spinboxes and the worldspawn into the global exchange
413                 msp.spinner1 = GTK_SPIN_BUTTON( spinnerMinX );
414                 msp.spinner2 = GTK_SPIN_BUTTON( spinnerMinY );
415                 msp.spinner3 = GTK_SPIN_BUTTON( spinnerMaxX );
416                 msp.spinner4 = GTK_SPIN_BUTTON( spinnerMaxY );
417                 msp.worldspawn = theWorldspawn;
418
419                 button = ui::Button( "Set" ); // create button with text
420                 button.connect( "clicked", G_CALLBACK( set_coordinates ), NULL ); // connect button with callback function
421                 gtk_table_attach_defaults( GTK_TABLE( table ), button, 1, 2, 3, 4 ); // insert button into table
422
423                 button = ui::Button( "Cancel" ); // create button with text
424                 button.connect( "clicked", G_CALLBACK( close_window ), NULL ); // connect button with callback function
425                 gtk_table_attach_defaults( GTK_TABLE( table ), button, 2, 3, 3, 4 ); // insert button into table
426         }
427         else {
428                 globalOutputStream() << "SunPlug: no worldspawn found!\n"; // output error to console
429
430                 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
431                 gtk_label_set_justify( GTK_LABEL( label ), GTK_JUSTIFY_LEFT ); // text align left
432                 gtk_box_pack_start( GTK_BOX( vbox ), label, FALSE, FALSE, 2 ); // insert the label in the box
433
434                 auto button = ui::Button( "OK" ); // create a button with text
435                 button.connect( "clicked", G_CALLBACK( close_window ), NULL ); // connect the click event to close the window
436                 gtk_box_pack_start( GTK_BOX( vbox ), button, FALSE, FALSE, 2 ); // insert the button in the box
437         }
438
439         gtk_window_set_position( GTK_WINDOW( window ), GTK_WIN_POS_CENTER ); // center the window
440         gtk_widget_show_all( window ); // show the window and all subelements
441 }