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