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