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