]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/gtkdlgs.cpp
Purge gtk_container_add
[xonotic/netradiant.git] / radiant / gtkdlgs.cpp
1 /*
2    Copyright (c) 2001, Loki software, inc.
3    All rights reserved.
4
5    Redistribution and use in source and binary forms, with or without modification,
6    are permitted provided that the following conditions are met:
7
8    Redistributions of source code must retain the above copyright notice, this list
9    of conditions and the following disclaimer.
10
11    Redistributions in binary form must reproduce the above copyright notice, this
12    list of conditions and the following disclaimer in the documentation and/or
13    other materials provided with the distribution.
14
15    Neither the name of Loki software nor the names of its contributors may be used
16    to endorse or promote products derived from this software without specific prior
17    written permission.
18
19    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
20    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22    DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
23    DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26    ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 //
32 // Some small dialogs that don't need much
33 //
34 // Leonardo Zide (leo@lokigames.com)
35 //
36
37 #include "gtkdlgs.h"
38
39 #include "debugging/debugging.h"
40 #include "version.h"
41 #include "aboutmsg.h"
42
43 #include "igl.h"
44 #include "iscenegraph.h"
45 #include "iselection.h"
46
47 #include <gdk/gdkkeysyms.h>
48 #include <uilib/uilib.h>
49
50 #include "os/path.h"
51 #include "math/aabb.h"
52 #include "container/array.h"
53 #include "generic/static.h"
54 #include "stream/stringstream.h"
55 #include "convert.h"
56 #include "gtkutil/messagebox.h"
57 #include "gtkutil/image.h"
58
59 #include "gtkmisc.h"
60 #include "brushmanip.h"
61 #include "build.h"
62 #include "qe3.h"
63 #include "texwindow.h"
64 #include "xywindow.h"
65 #include "mainframe.h"
66 #include "preferences.h"
67 #include "url.h"
68 #include "cmdlib.h"
69
70
71
72 // =============================================================================
73 // Project settings dialog
74
75 class GameComboConfiguration
76 {
77 public:
78 const char* basegame_dir;
79 const char* basegame;
80 const char* known_dir;
81 const char* known;
82 const char* custom;
83
84 GameComboConfiguration() :
85         basegame_dir( g_pGameDescription->getRequiredKeyValue( "basegame" ) ),
86         basegame( g_pGameDescription->getRequiredKeyValue( "basegamename" ) ),
87         known_dir( g_pGameDescription->getKeyValue( "knowngame" ) ),
88         known( g_pGameDescription->getKeyValue( "knowngamename" ) ),
89         custom( g_pGameDescription->getRequiredKeyValue( "unknowngamename" ) ){
90 }
91 };
92
93 typedef LazyStatic<GameComboConfiguration> LazyStaticGameComboConfiguration;
94
95 inline GameComboConfiguration& globalGameComboConfiguration(){
96         return LazyStaticGameComboConfiguration::instance();
97 }
98
99
100 struct gamecombo_t
101 {
102         gamecombo_t( int _game, const char* _fs_game, bool _sensitive )
103                 : game( _game ), fs_game( _fs_game ), sensitive( _sensitive )
104         {}
105         int game;
106         const char* fs_game;
107         bool sensitive;
108 };
109
110 gamecombo_t gamecombo_for_dir( const char* dir ){
111         if ( string_equal( dir, globalGameComboConfiguration().basegame_dir ) ) {
112                 return gamecombo_t( 0, "", false );
113         }
114         else if ( string_equal( dir, globalGameComboConfiguration().known_dir ) ) {
115                 return gamecombo_t( 1, dir, false );
116         }
117         else
118         {
119                 return gamecombo_t( string_empty( globalGameComboConfiguration().known_dir ) ? 1 : 2, dir, true );
120         }
121 }
122
123 gamecombo_t gamecombo_for_gamename( const char* gamename ){
124         if ( ( strlen( gamename ) == 0 ) || !strcmp( gamename, globalGameComboConfiguration().basegame ) ) {
125                 return gamecombo_t( 0, "", false );
126         }
127         else if ( !strcmp( gamename, globalGameComboConfiguration().known ) ) {
128                 return gamecombo_t( 1, globalGameComboConfiguration().known_dir, false );
129         }
130         else
131         {
132                 return gamecombo_t( string_empty( globalGameComboConfiguration().known_dir ) ? 1 : 2, "", true );
133         }
134 }
135
136 inline void path_copy_clean( char* destination, const char* source ){
137         char* i = destination;
138
139         while ( *source != '\0' )
140         {
141                 *i++ = ( *source == '\\' ) ? '/' : *source;
142                 ++source;
143         }
144
145         if ( i != destination && *( i - 1 ) != '/' ) {
146                 *( i++ ) = '/';
147         }
148
149         *i = '\0';
150 }
151
152
153 struct GameCombo
154 {
155         ui::ComboBoxText game_select;
156         GtkEntry* fsgame_entry;
157 };
158
159 gboolean OnSelchangeComboWhatgame( ui::Widget widget, GameCombo* combo ){
160         const char *gamename;
161         {
162                 GtkTreeIter iter;
163                 gtk_combo_box_get_active_iter( combo->game_select, &iter );
164                 gtk_tree_model_get( gtk_combo_box_get_model( combo->game_select ), &iter, 0, (gpointer*)&gamename, -1 );
165         }
166
167         gamecombo_t gamecombo = gamecombo_for_gamename( gamename );
168
169         gtk_entry_set_text( combo->fsgame_entry, gamecombo.fs_game );
170         gtk_widget_set_sensitive( GTK_WIDGET( combo->fsgame_entry ), gamecombo.sensitive );
171
172         return FALSE;
173 }
174
175 class MappingMode
176 {
177 public:
178 bool do_mapping_mode;
179 const char* sp_mapping_mode;
180 const char* mp_mapping_mode;
181
182 MappingMode() :
183         do_mapping_mode( !string_empty( g_pGameDescription->getKeyValue( "show_gamemode" ) ) ),
184         sp_mapping_mode( "Single Player mapping mode" ),
185         mp_mapping_mode( "Multiplayer mapping mode" ){
186 }
187 };
188
189 typedef LazyStatic<MappingMode> LazyStaticMappingMode;
190
191 inline MappingMode& globalMappingMode(){
192         return LazyStaticMappingMode::instance();
193 }
194
195 class ProjectSettingsDialog
196 {
197 public:
198 GameCombo game_combo;
199 GtkComboBox* gamemode_combo;
200 };
201
202 ui::Window ProjectSettingsDialog_construct( ProjectSettingsDialog& dialog, ModalDialog& modal ){
203         auto window = MainFrame_getWindow().create_dialog_window("Project Settings", G_CALLBACK(dialog_delete_callback ), &modal );
204
205         {
206                 auto table1 = create_dialog_table( 1, 2, 4, 4, 4 );
207                 window.add(table1);
208                 {
209                         GtkVBox* vbox = create_dialog_vbox( 4 );
210                         gtk_table_attach( table1, GTK_WIDGET( vbox ), 1, 2, 0, 1,
211                                                           (GtkAttachOptions) ( GTK_FILL ),
212                                                           (GtkAttachOptions) ( GTK_FILL ), 0, 0 );
213                         {
214                                 GtkButton* button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &modal );
215                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
216                         }
217                         {
218                                 GtkButton* button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &modal );
219                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
220                         }
221                 }
222                 {
223                         auto frame = create_dialog_frame( "Project settings" );
224                         gtk_table_attach( table1, GTK_WIDGET( frame ), 0, 1, 0, 1,
225                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
226                                                           (GtkAttachOptions) ( GTK_FILL ), 0, 0 );
227                         {
228                                 auto table2 = create_dialog_table( ( globalMappingMode().do_mapping_mode ) ? 4 : 3, 2, 4, 4, 4 );
229                                 frame.add(table2);
230
231                                 {
232                                         auto label = ui::Label( "Select mod" );
233                                         label.show();
234                                         gtk_table_attach( table2, GTK_WIDGET( label ), 0, 1, 0, 1,
235                                                                           (GtkAttachOptions) ( GTK_FILL ),
236                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
237                                         gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
238                                 }
239                                 {
240                                         dialog.game_combo.game_select = ui::ComboBoxText();
241
242                                         gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().basegame );
243                                         if ( globalGameComboConfiguration().known[0] != '\0' ) {
244                                                 gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().known );
245                                         }
246                                         gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().custom );
247
248                                         dialog.game_combo.game_select.show();
249                                         gtk_table_attach( table2, GTK_WIDGET( dialog.game_combo.game_select ), 1, 2, 0, 1,
250                                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
251                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
252
253                                         g_signal_connect( G_OBJECT( dialog.game_combo.game_select ), "changed", G_CALLBACK( OnSelchangeComboWhatgame ), &dialog.game_combo );
254                                 }
255
256                                 {
257                                         auto label = ui::Label( "fs_game" );
258                                         label.show();
259                                         gtk_table_attach( table2, GTK_WIDGET( label ), 0, 1, 1, 2,
260                                                                           (GtkAttachOptions) ( GTK_FILL ),
261                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
262                                         gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
263                                 }
264                                 {
265                                         auto entry = ui::Entry();
266                                         entry.show();
267                                         gtk_table_attach( table2, GTK_WIDGET( entry ), 1, 2, 1, 2,
268                                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
269                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
270
271                                         dialog.game_combo.fsgame_entry = entry;
272                                 }
273
274                                 if ( globalMappingMode().do_mapping_mode ) {
275                                         auto label = ui::Label( "Mapping mode" );
276                                         label.show();
277                                         gtk_table_attach( table2, GTK_WIDGET( label ), 0, 1, 3, 4,
278                                                                           (GtkAttachOptions) ( GTK_FILL ),
279                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
280                                         gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
281
282                                         auto combo = ui::ComboBoxText();
283                                         gtk_combo_box_text_append_text( combo, globalMappingMode().sp_mapping_mode );
284                                         gtk_combo_box_text_append_text( combo, globalMappingMode().mp_mapping_mode );
285
286                                         combo.show();
287                                         gtk_table_attach( table2, GTK_WIDGET( combo ), 1, 2, 3, 4,
288                                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
289                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
290
291                                         dialog.gamemode_combo = combo;
292                                 }
293                         }
294                 }
295         }
296
297         // initialise the fs_game selection from the project settings into the dialog
298         const char* dir = gamename_get();
299         gamecombo_t gamecombo = gamecombo_for_dir( dir );
300
301         gtk_combo_box_set_active( dialog.game_combo.game_select, gamecombo.game );
302         gtk_entry_set_text( dialog.game_combo.fsgame_entry, gamecombo.fs_game );
303         gtk_widget_set_sensitive( GTK_WIDGET( dialog.game_combo.fsgame_entry ), gamecombo.sensitive );
304
305         if ( globalMappingMode().do_mapping_mode ) {
306                 const char *gamemode = gamemode_get();
307                 if ( string_empty( gamemode ) || string_equal( gamemode, "sp" ) ) {
308                         gtk_combo_box_set_active( dialog.gamemode_combo, 0 );
309                 }
310                 else
311                 {
312                         gtk_combo_box_set_active( dialog.gamemode_combo, 1 );
313                 }
314         }
315
316         return window;
317 }
318
319 void ProjectSettingsDialog_ok( ProjectSettingsDialog& dialog ){
320         const char* dir = gtk_entry_get_text( dialog.game_combo.fsgame_entry );
321
322         const char* new_gamename = path_equal( dir, globalGameComboConfiguration().basegame_dir )
323                                                            ? ""
324                                                            : dir;
325
326         if ( !path_equal( new_gamename, gamename_get() ) ) {
327                 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Changing Game Name" );
328
329                 EnginePath_Unrealise();
330
331                 gamename_set( new_gamename );
332
333                 EnginePath_Realise();
334         }
335
336         if ( globalMappingMode().do_mapping_mode ) {
337                 // read from gamemode_combo
338                 int active = gtk_combo_box_get_active( dialog.gamemode_combo );
339                 if ( active == -1 || active == 0 ) {
340                         gamemode_set( "sp" );
341                 }
342                 else
343                 {
344                         gamemode_set( "mp" );
345                 }
346         }
347 }
348
349 void DoProjectSettings(){
350         if ( ConfirmModified( "Edit Project Settings" ) ) {
351                 ModalDialog modal;
352                 ProjectSettingsDialog dialog;
353
354                 ui::Window window = ProjectSettingsDialog_construct( dialog, modal );
355
356                 if ( modal_dialog_show( window, modal ) == eIDOK ) {
357                         ProjectSettingsDialog_ok( dialog );
358                 }
359
360                 gtk_widget_destroy( GTK_WIDGET( window ) );
361         }
362 }
363
364 // =============================================================================
365 // Arbitrary Sides dialog
366
367 void DoSides( int type, int axis ){
368         ModalDialog dialog;
369         GtkEntry* sides_entry;
370
371         auto window = MainFrame_getWindow().create_dialog_window("Arbitrary sides", G_CALLBACK(dialog_delete_callback ), &dialog );
372
373         auto accel = ui::AccelGroup();
374         window.add_accel_group( accel );
375
376         {
377                 auto hbox = create_dialog_hbox( 4, 4 );
378                 window.add(hbox);
379                 {
380                         auto label = ui::Label( "Sides:" );
381                         label.show();
382                         gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
383                 }
384                 {
385                         auto entry = ui::Entry();
386                         entry.show();
387                         gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( entry ), FALSE, FALSE, 0 );
388                         sides_entry = entry;
389                         gtk_widget_grab_focus( GTK_WIDGET( entry ) );
390                 }
391                 {
392                         GtkVBox* vbox = create_dialog_vbox( 4 );
393                         gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), TRUE, TRUE, 0 );
394                         {
395                                 auto button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &dialog );
396                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
397                                 widget_make_default( button );
398                                 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
399                         }
400                         {
401                                 GtkButton* button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &dialog );
402                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
403                                 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
404                         }
405                 }
406         }
407
408         if ( modal_dialog_show( window, dialog ) == eIDOK ) {
409                 const char *str = gtk_entry_get_text( sides_entry );
410
411                 Scene_BrushConstructPrefab( GlobalSceneGraph(), (EBrushPrefab)type, atoi( str ), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
412         }
413
414         gtk_widget_destroy( GTK_WIDGET( window ) );
415 }
416
417 // =============================================================================
418 // About dialog (no program is complete without one)
419
420 void about_button_changelog( ui::Widget widget, gpointer data ){
421         StringOutputStream log( 256 );
422         log << "https://gitlab.com/xonotic/netradiant/commits/master";
423         OpenURL( log.c_str() );
424 }
425
426 void about_button_credits( ui::Widget widget, gpointer data ){
427         StringOutputStream cred( 256 );
428         cred << "https://gitlab.com/xonotic/netradiant/graphs/master";
429         OpenURL( cred.c_str() );
430 }
431
432 void about_button_issues( GtkWidget *widget, gpointer data ){
433         StringOutputStream cred( 256 );
434         cred << "https://gitlab.com/xonotic/netradiant/issues";
435         OpenURL( cred.c_str() );
436 }
437
438 void DoAbout(){
439         ModalDialog dialog;
440         ModalDialogButton ok_button( dialog, eIDOK );
441
442         auto window = MainFrame_getWindow().create_modal_dialog_window("About NetRadiant", dialog );
443
444         {
445                 auto vbox = create_dialog_vbox( 4, 4 );
446                 window.add(vbox);
447
448                 {
449                         GtkHBox* hbox = create_dialog_hbox( 4 );
450                         gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), FALSE, TRUE, 0 );
451
452                         {
453                                 GtkVBox* vbox2 = create_dialog_vbox( 4 );
454                                 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox2 ), TRUE, FALSE, 0 );
455                                 {
456                                         auto frame = create_dialog_frame( 0, GTK_SHADOW_IN );
457                                         gtk_box_pack_start( GTK_BOX( vbox2 ), GTK_WIDGET( frame ), FALSE, FALSE, 0 );
458                                         {
459                                                 auto image = new_local_image( "logo.png" );
460                                                 image.show();
461                                                 frame.add(image);
462                                         }
463                                 }
464                         }
465
466                         {
467                                 char const *label_text = "NetRadiant " RADIANT_VERSION "\n"
468                                                                                 __DATE__ "\n\n"
469                                         RADIANT_ABOUTMSG "\n\n"
470                                                                                 "This program is free software\n"
471                                                                                 "licensed under the GNU GPL.\n\n"
472                                                                                 "NetRadiant is unsupported, however\n"
473                                                                                 "you may report your problems at\n"
474                                                                                 "https://gitlab.com/xonotic/netradiant/issues";
475
476                                 auto label = ui::Label( label_text );
477
478                                 label.show();
479                                 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
480                                 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
481                                 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
482                         }
483
484                         {
485                                 GtkVBox* vbox2 = create_dialog_vbox( 4 );
486                                 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox2 ), FALSE, TRUE, 0 );
487                                 {
488                                         GtkButton* button = create_modal_dialog_button( "OK", ok_button );
489                                         gtk_box_pack_start( GTK_BOX( vbox2 ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
490                                 }
491                                 {
492                                         GtkButton* button = create_dialog_button( "Credits", G_CALLBACK( about_button_credits ), 0 );
493                                         gtk_box_pack_start( GTK_BOX( vbox2 ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
494                                 }
495                                 {
496                                         GtkButton* button = create_dialog_button( "Changes", G_CALLBACK( about_button_changelog ), 0 );
497                                         gtk_box_pack_start( GTK_BOX( vbox2 ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
498                                 }
499                                 {
500                                         GtkButton* button = create_dialog_button( "Issues", G_CALLBACK( about_button_issues ), 0 );
501                                         gtk_box_pack_start( GTK_BOX( vbox2 ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
502                                 }
503                         }
504                 }
505                 {
506                         auto frame = create_dialog_frame( "OpenGL Properties" );
507                         gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( frame ), FALSE, FALSE, 0 );
508                         {
509                                 auto table = create_dialog_table( 3, 2, 4, 4, 4 );
510                                 frame.add(table);
511                                 {
512                                         auto label = ui::Label( "Vendor:" );
513                                         label.show();
514                                         gtk_table_attach( table, GTK_WIDGET( label ), 0, 1, 0, 1,
515                                                                           (GtkAttachOptions) ( GTK_FILL ),
516                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
517                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
518                                 }
519                                 {
520                                         auto label = ui::Label( "Version:" );
521                                         label.show();
522                                         gtk_table_attach( table, GTK_WIDGET( label ), 0, 1, 1, 2,
523                                                                           (GtkAttachOptions) ( GTK_FILL ),
524                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
525                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
526                                 }
527                                 {
528                                         auto label = ui::Label( "Renderer:" );
529                                         label.show();
530                                         gtk_table_attach( table, GTK_WIDGET( label ), 0, 1, 2, 3,
531                                                                           (GtkAttachOptions) ( GTK_FILL ),
532                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
533                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
534                                 }
535                                 {
536                                         auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VENDOR ) ) );
537                                         label.show();
538                                         gtk_table_attach( table, GTK_WIDGET( label ), 1, 2, 0, 1,
539                                                                           (GtkAttachOptions) ( GTK_FILL ),
540                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
541                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
542                                 }
543                                 {
544                                         auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VERSION ) ) );
545                                         label.show();
546                                         gtk_table_attach( table, GTK_WIDGET( label ), 1, 2, 1, 2,
547                                                                           (GtkAttachOptions) ( GTK_FILL ),
548                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
549                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
550                                 }
551                                 {
552                                         auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_RENDERER ) ) );
553                                         label.show();
554                                         gtk_table_attach( table, GTK_WIDGET( label ), 1, 2, 2, 3,
555                                                                           (GtkAttachOptions) ( GTK_FILL ),
556                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
557                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
558                                 }
559                         }
560                         {
561                                 auto frame = create_dialog_frame( "OpenGL Extensions" );
562                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( frame ), TRUE, TRUE, 0 );
563                                 {
564                                         auto sc_extensions = create_scrolled_window( GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, 4 );
565                                         frame.add(sc_extensions);
566                                         {
567                                                 auto text_extensions = ui::TextView();
568                                                 gtk_text_view_set_editable( GTK_TEXT_VIEW( text_extensions ), FALSE );
569                                                 sc_extensions.add(text_extensions);
570                                                 GtkTextBuffer* buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW( text_extensions ) );
571                                                 gtk_text_buffer_set_text( buffer, reinterpret_cast<const char*>( glGetString( GL_EXTENSIONS ) ), -1 );
572                                                 gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW( text_extensions ), GTK_WRAP_WORD );
573                                                 text_extensions.show();
574                                         }
575                                 }
576                         }
577                 }
578         }
579
580         modal_dialog_show( window, dialog );
581
582         gtk_widget_destroy( GTK_WIDGET( window ) );
583 }
584
585 // =============================================================================
586 // TextureLayout dialog
587
588 // Last used texture scale values
589 static float last_used_texture_layout_scale_x = 4.0;
590 static float last_used_texture_layout_scale_y = 4.0;
591
592 EMessageBoxReturn DoTextureLayout( float *fx, float *fy ){
593         ModalDialog dialog;
594         ModalDialogButton ok_button( dialog, eIDOK );
595         ModalDialogButton cancel_button( dialog, eIDCANCEL );
596         GtkEntry* x;
597         GtkEntry* y;
598
599         auto window = MainFrame_getWindow().create_modal_dialog_window("Patch texture layout", dialog );
600
601         auto accel = ui::AccelGroup();
602         window.add_accel_group( accel );
603
604         {
605                 auto hbox = create_dialog_hbox( 4, 4 );
606                 window.add(hbox);
607                 {
608                         GtkVBox* vbox = create_dialog_vbox( 4 );
609                         gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), TRUE, TRUE, 0 );
610                         {
611                                 auto label = ui::Label( "Texture will be fit across the patch based\n"
612                                                                                                                         "on the x and y values given. Values of 1x1\n"
613                                                                                                                         "will \"fit\" the texture. 2x2 will repeat\n"
614                                                                                                                         "it twice, etc." );
615                                 label.show();
616                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), TRUE, TRUE, 0 );
617                                 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
618                         }
619                         {
620                                 auto table = create_dialog_table( 2, 2, 4, 4 );
621                                 table.show();
622                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
623                                 {
624                                         auto label = ui::Label( "Texture x:" );
625                                         label.show();
626                                         gtk_table_attach( table, GTK_WIDGET( label ), 0, 1, 0, 1,
627                                                                           (GtkAttachOptions) ( GTK_FILL ),
628                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
629                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
630                                 }
631                                 {
632                                         auto label = ui::Label( "Texture y:" );
633                                         label.show();
634                                         gtk_table_attach( table, GTK_WIDGET( label ), 0, 1, 1, 2,
635                                                                           (GtkAttachOptions) ( GTK_FILL ),
636                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
637                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
638                                 }
639                                 {
640                                         auto entry = ui::Entry();
641                                         entry.show();
642                                         gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 0, 1,
643                                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
644                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
645
646                                         x = entry;
647                                 }
648                                 {
649                                         auto entry = ui::Entry();
650                                         entry.show();
651                                         gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 1, 2,
652                                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
653                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
654
655                                         y = entry;
656                                 }
657                         }
658                 }
659                 {
660                         GtkVBox* vbox = create_dialog_vbox( 4 );
661                         gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), FALSE, FALSE, 0 );
662                         {
663                                 auto button = create_modal_dialog_button( "OK", ok_button );
664                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
665                                 widget_make_default( button );
666                                 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
667                         }
668                         {
669                                 GtkButton* button = create_modal_dialog_button( "Cancel", cancel_button );
670                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
671                                 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
672                         }
673                 }
674         }
675         
676         // Initialize with last used values
677         char buf[16];
678         
679         sprintf( buf, "%f", last_used_texture_layout_scale_x );
680         gtk_entry_set_text( x, buf );
681         
682         sprintf( buf, "%f", last_used_texture_layout_scale_y );
683         gtk_entry_set_text( y, buf );
684
685         // Set focus after intializing the values
686         gtk_widget_grab_focus( GTK_WIDGET( x ) );
687
688         EMessageBoxReturn ret = modal_dialog_show( window, dialog );
689         if ( ret == eIDOK ) {
690                 *fx = static_cast<float>( atof( gtk_entry_get_text( x ) ) );
691                 *fy = static_cast<float>( atof( gtk_entry_get_text( y ) ) );
692         
693                 // Remember last used values
694                 last_used_texture_layout_scale_x = *fx;
695                 last_used_texture_layout_scale_y = *fy;
696         }
697
698         gtk_widget_destroy( GTK_WIDGET( window ) );
699
700         return ret;
701 }
702
703 // =============================================================================
704 // Text Editor dialog
705
706 // master window widget
707 static ui::Widget text_editor;
708 static ui::Widget text_widget; // slave, text widget from the gtk editor
709
710 static gint editor_delete( ui::Widget widget, gpointer data ){
711         if ( widget.alert( "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
712                 return TRUE;
713         }
714
715         gtk_widget_hide( text_editor );
716
717         return TRUE;
718 }
719
720 static void editor_save( ui::Widget widget, gpointer data ){
721         FILE *f = fopen( (char*)g_object_get_data( G_OBJECT( data ), "filename" ), "w" );
722         gpointer text = g_object_get_data( G_OBJECT( data ), "text" );
723
724         if ( f == 0 ) {
725                 ui::Widget(GTK_WIDGET( data )).alert( "Error saving file !" );
726                 return;
727         }
728
729         char *str = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
730         fwrite( str, 1, strlen( str ), f );
731         fclose( f );
732 }
733
734 static void editor_close( ui::Widget widget, gpointer data ){
735         if ( text_editor.alert( "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
736                 return;
737         }
738
739         gtk_widget_hide( text_editor );
740 }
741
742 static void CreateGtkTextEditor(){
743         ui::Widget vbox, hbox, button, text;
744
745         auto dlg = ui::Window( ui::window_type::TOP );
746
747         g_signal_connect( G_OBJECT( dlg ), "delete_event",
748                                           G_CALLBACK( editor_delete ), 0 );
749         gtk_window_set_default_size( GTK_WINDOW( dlg ), 600, 300 );
750
751         vbox = ui::VBox( FALSE, 5 );
752         vbox.show();
753         dlg.add(vbox);
754         gtk_container_set_border_width( GTK_CONTAINER( vbox ), 5 );
755
756         auto scr = ui::ScrolledWindow();
757         scr.show();
758         gtk_box_pack_start( GTK_BOX( vbox ), scr, TRUE, TRUE, 0 );
759         gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scr ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
760         gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( scr ), GTK_SHADOW_IN );
761
762         text = ui::TextView();
763         scr.add(text);
764         text.show();
765         g_object_set_data( G_OBJECT( dlg ), "text", (gpointer) text );
766         gtk_text_view_set_editable( GTK_TEXT_VIEW( text ), TRUE );
767
768         hbox = ui::HBox( FALSE, 5 );
769         hbox.show();
770         gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), FALSE, TRUE, 0 );
771
772         button = ui::Button( "Close" );
773         button.show();
774         gtk_box_pack_end( GTK_BOX( hbox ), button, FALSE, FALSE, 0 );
775         g_signal_connect( G_OBJECT( button ), "clicked",
776                                           G_CALLBACK( editor_close ), dlg );
777         gtk_widget_set_size_request( button, 60, -1 );
778
779         button = ui::Button( "Save" );
780         button.show();
781         gtk_box_pack_end( GTK_BOX( hbox ), button, FALSE, FALSE, 0 );
782         g_signal_connect( G_OBJECT( button ), "clicked",
783                                           G_CALLBACK( editor_save ), dlg );
784         gtk_widget_set_size_request( button, 60, -1 );
785
786         text_editor = dlg;
787         text_widget = text;
788 }
789
790 static void DoGtkTextEditor( const char* filename, guint cursorpos ){
791         if ( !text_editor ) {
792                 CreateGtkTextEditor(); // build it the first time we need it
793
794         }
795         // Load file
796         FILE *f = fopen( filename, "r" );
797
798         if ( f == 0 ) {
799                 globalOutputStream() << "Unable to load file " << filename << " in shader editor.\n";
800                 gtk_widget_hide( text_editor );
801         }
802         else
803         {
804                 fseek( f, 0, SEEK_END );
805                 int len = ftell( f );
806                 void *buf = malloc( len );
807                 void *old_filename;
808
809                 rewind( f );
810                 fread( buf, 1, len, f );
811
812                 gtk_window_set_title( GTK_WINDOW( text_editor ), filename );
813
814                 GtkTextBuffer* text_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW( text_widget ) );
815                 gtk_text_buffer_set_text( text_buffer, (char*)buf, len );
816
817                 old_filename = g_object_get_data( G_OBJECT( text_editor ), "filename" );
818                 if ( old_filename ) {
819                         free( old_filename );
820                 }
821                 g_object_set_data( G_OBJECT( text_editor ), "filename", strdup( filename ) );
822
823                 // trying to show later
824                 text_editor.show();
825
826 #ifdef WIN32
827                 process_gui();
828 #endif
829
830                 // only move the cursor if it's not exceeding the size..
831                 // NOTE: this is erroneous, cursorpos is the offset in bytes, not in characters
832                 // len is the max size in bytes, not in characters either, but the character count is below that limit..
833                 // thinking .. the difference between character count and byte count would be only because of CR/LF?
834                 {
835                         GtkTextIter text_iter;
836                         // character offset, not byte offset
837                         gtk_text_buffer_get_iter_at_offset( text_buffer, &text_iter, cursorpos );
838                         gtk_text_buffer_place_cursor( text_buffer, &text_iter );
839                 }
840
841 #ifdef WIN32
842                 gtk_widget_queue_draw( text_widget );
843 #endif
844
845                 free( buf );
846                 fclose( f );
847         }
848 }
849
850 // =============================================================================
851 // Light Intensity dialog
852
853 EMessageBoxReturn DoLightIntensityDlg( int *intensity ){
854         ModalDialog dialog;
855         GtkEntry* intensity_entry;
856         ModalDialogButton ok_button( dialog, eIDOK );
857         ModalDialogButton cancel_button( dialog, eIDCANCEL );
858
859         ui::Window window = MainFrame_getWindow().create_modal_dialog_window("Light intensity", dialog, -1, -1 );
860
861         auto accel_group = ui::AccelGroup();
862         window.add_accel_group( accel_group );
863
864         {
865                 auto hbox = create_dialog_hbox( 4, 4 );
866                 window.add(hbox);
867                 {
868                         GtkVBox* vbox = create_dialog_vbox( 4 );
869                         gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), TRUE, TRUE, 0 );
870                         {
871                                 auto label = ui::Label( "ESC for default, ENTER to validate" );
872                                 label.show();
873                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
874                         }
875                         {
876                                 auto entry = ui::Entry();
877                                 entry.show();
878                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( entry ), TRUE, TRUE, 0 );
879
880                                 gtk_widget_grab_focus( GTK_WIDGET( entry ) );
881
882                                 intensity_entry = entry;
883                         }
884                 }
885                 {
886                         GtkVBox* vbox = create_dialog_vbox( 4 );
887                         gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), FALSE, FALSE, 0 );
888
889                         {
890                                 auto button = create_modal_dialog_button( "OK", ok_button );
891                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
892                                 widget_make_default( button );
893                                 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel_group, GDK_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
894                         }
895                         {
896                                 GtkButton* button = create_modal_dialog_button( "Cancel", cancel_button );
897                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
898                                 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel_group, GDK_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
899                         }
900                 }
901         }
902
903         char buf[16];
904         sprintf( buf, "%d", *intensity );
905         gtk_entry_set_text( intensity_entry, buf );
906
907         EMessageBoxReturn ret = modal_dialog_show( window, dialog );
908         if ( ret == eIDOK ) {
909                 *intensity = atoi( gtk_entry_get_text( intensity_entry ) );
910         }
911
912         gtk_widget_destroy( GTK_WIDGET( window ) );
913
914         return ret;
915 }
916
917 // =============================================================================
918 // Add new shader tag dialog
919
920 EMessageBoxReturn DoShaderTagDlg( CopiedString* tag, char* title ){
921         ModalDialog dialog;
922         GtkEntry* textentry;
923         ModalDialogButton ok_button( dialog, eIDOK );
924         ModalDialogButton cancel_button( dialog, eIDCANCEL );
925
926         auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
927
928         auto accel_group = ui::AccelGroup();
929         window.add_accel_group( accel_group );
930
931         {
932                 auto hbox = create_dialog_hbox( 4, 4 );
933                 window.add(hbox);
934                 {
935                         GtkVBox* vbox = create_dialog_vbox( 4 );
936                         gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), TRUE, TRUE, 0 );
937                         {
938                                 //GtkLabel* label = GTK_LABEL(gtk_label_new("Enter one ore more tags separated by spaces"));
939                                 auto label = ui::Label( "ESC to cancel, ENTER to validate" );
940                                 label.show();
941                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
942                         }
943                         {
944                                 auto entry = ui::Entry();
945                                 entry.show();
946                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( entry ), TRUE, TRUE, 0 );
947
948                                 gtk_widget_grab_focus( GTK_WIDGET( entry ) );
949
950                                 textentry = entry;
951                         }
952                 }
953                 {
954                         GtkVBox* vbox = create_dialog_vbox( 4 );
955                         gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), FALSE, FALSE, 0 );
956
957                         {
958                                 auto button = create_modal_dialog_button( "OK", ok_button );
959                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
960                                 widget_make_default( button );
961                                 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel_group, GDK_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
962                         }
963                         {
964                                 GtkButton* button = create_modal_dialog_button( "Cancel", cancel_button );
965                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
966                                 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel_group, GDK_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
967                         }
968                 }
969         }
970
971         EMessageBoxReturn ret = modal_dialog_show( window, dialog );
972         if ( ret == eIDOK ) {
973                 *tag = gtk_entry_get_text( textentry );
974         }
975
976         gtk_widget_destroy( GTK_WIDGET( window ) );
977
978         return ret;
979 }
980
981 EMessageBoxReturn DoShaderInfoDlg( const char* name, const char* filename, char* title ){
982         ModalDialog dialog;
983         ModalDialogButton ok_button( dialog, eIDOK );
984
985         auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
986
987         auto accel_group = ui::AccelGroup();
988         window.add_accel_group( accel_group );
989
990         {
991                 auto hbox = create_dialog_hbox( 4, 4 );
992                 window.add(hbox);
993                 {
994                         GtkVBox* vbox = create_dialog_vbox( 4 );
995                         gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), FALSE, FALSE, 0 );
996                         {
997                                 auto label = ui::Label( "The selected shader" );
998                                 label.show();
999                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
1000                         }
1001                         {
1002                                 auto label = ui::Label( name );
1003                                 label.show();
1004                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
1005                         }
1006                         {
1007                                 auto label = ui::Label( "is located in file" );
1008                                 label.show();
1009                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
1010                         }
1011                         {
1012                                 auto label = ui::Label( filename );
1013                                 label.show();
1014                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
1015                         }
1016                         {
1017                                 auto button = create_modal_dialog_button( "OK", ok_button );
1018                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
1019                                 widget_make_default( button );
1020                                 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel_group, GDK_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
1021                         }
1022                 }
1023         }
1024
1025         EMessageBoxReturn ret = modal_dialog_show( window, dialog );
1026
1027         gtk_widget_destroy( GTK_WIDGET( window ) );
1028
1029         return ret;
1030 }
1031
1032
1033
1034 #ifdef WIN32
1035 #include <gdk/gdkwin32.h>
1036 #endif
1037
1038 #ifdef WIN32
1039 // use the file associations to open files instead of builtin Gtk editor
1040 bool g_TextEditor_useWin32Editor = true;
1041 #else
1042 // custom shader editor
1043 bool g_TextEditor_useCustomEditor = false;
1044 CopiedString g_TextEditor_editorCommand( "" );
1045 #endif
1046
1047 void DoTextEditor( const char* filename, int cursorpos ){
1048 #ifdef WIN32
1049         if ( g_TextEditor_useWin32Editor ) {
1050                 globalOutputStream() << "opening file '" << filename << "' (line " << cursorpos << " info ignored)\n";
1051                 ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", filename, 0, 0, SW_SHOW );
1052                 return;
1053         }
1054 #else
1055         // check if a custom editor is set
1056         if ( g_TextEditor_useCustomEditor && !g_TextEditor_editorCommand.empty() ) {
1057                 StringOutputStream strEditCommand( 256 );
1058                 strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << filename << "\"";
1059
1060                 globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n";
1061                 // note: linux does not return false if the command failed so it will assume success
1062                 if ( Q_Exec( 0, const_cast<char*>( strEditCommand.c_str() ), 0, true, false ) == false ) {
1063                         globalOutputStream() << "Failed to execute " << strEditCommand.c_str() << ", using default\n";
1064                 }
1065                 else
1066                 {
1067                         // the command (appeared) to run successfully, no need to do anything more
1068                         return;
1069                 }
1070         }
1071 #endif
1072
1073         DoGtkTextEditor( filename, cursorpos );
1074 }