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