]> git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/gtkdlgs.cpp
Merge commit '5a8c27d93c0c57243722ade7aa3ca1f696de46f2' into garux-merge
[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( "delete_event",
761                                           G_CALLBACK( editor_delete ), 0 );
762         gtk_window_set_default_size( dlg, 400, 300 );
763
764         auto vbox = ui::VBox( FALSE, 5 );
765         vbox.show();
766         dlg.add(vbox);
767         gtk_container_set_border_width( GTK_CONTAINER( vbox ), 5 );
768
769         auto scr = ui::ScrolledWindow(ui::New);
770         scr.show();
771         vbox.pack_start( scr, TRUE, TRUE, 0 );
772         gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scr ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
773         gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( scr ), GTK_SHADOW_IN );
774
775         auto text = ui::TextView(ui::New);
776         scr.add(text);
777         text.show();
778         g_object_set_data( G_OBJECT( dlg ), "text", (gpointer) text );
779         gtk_text_view_set_editable( text, TRUE );
780
781         auto hbox = ui::HBox( FALSE, 5 );
782         hbox.show();
783         vbox.pack_start( hbox, FALSE, TRUE, 0 );
784
785         auto button = ui::Button( "Close" );
786         button.show();
787         hbox.pack_end(button, FALSE, FALSE, 0);
788         button.connect( "clicked",
789                                           G_CALLBACK( editor_close ), dlg );
790         button.dimensions(60, -1);
791
792         button = ui::Button( "Save" );
793         button.show();
794         hbox.pack_end(button, FALSE, FALSE, 0);
795         button.connect( "clicked",
796                                           G_CALLBACK( editor_save ), dlg );
797         button.dimensions(60, -1);
798
799         text_editor = dlg;
800         text_widget = text;
801 }
802
803 static void DoGtkTextEditor( const char* filename, guint cursorpos, int length ){
804         if ( !text_editor ) {
805                 CreateGtkTextEditor(); // build it the first time we need it
806
807         }
808         // Load file
809         FILE *f = fopen( filename, "r" );
810
811         if ( f == 0 ) {
812                 globalOutputStream() << "Unable to load file " << filename << " in shader editor.\n";
813                 text_editor.hide();
814         }
815         else
816         {
817                 fseek( f, 0, SEEK_END );
818                 int len = ftell( f );
819                 void *buf = malloc( len );
820                 void *old_filename;
821
822                 rewind( f );
823                 fread( buf, 1, len, f );
824
825                 gtk_window_set_title( text_editor, filename );
826
827                 auto text_buffer = gtk_text_view_get_buffer(ui::TextView::from(text_widget));
828                 gtk_text_buffer_set_text( text_buffer, (char*)buf, length );
829
830                 old_filename = g_object_get_data( G_OBJECT( text_editor ), "filename" );
831                 if ( old_filename ) {
832                         free( old_filename );
833                 }
834                 g_object_set_data( G_OBJECT( text_editor ), "filename", strdup( filename ) );
835
836                 // trying to show later
837                 text_editor.show();
838                 gtk_window_present( GTK_WINDOW( text_editor ) );
839
840 #if GDEF_OS_WINDOWS
841                 ui::process();
842 #endif
843
844                 // only move the cursor if it's not exceeding the size..
845                 // NOTE: this is erroneous, cursorpos is the offset in bytes, not in characters
846                 // len is the max size in bytes, not in characters either, but the character count is below that limit..
847                 // thinking .. the difference between character count and byte count would be only because of CR/LF?
848                 {
849                         GtkTextIter text_iter;
850                         // character offset, not byte offset
851                         gtk_text_buffer_get_iter_at_offset( text_buffer, &text_iter, cursorpos );
852                         gtk_text_buffer_place_cursor( text_buffer, &text_iter );
853                         gtk_text_view_scroll_to_iter( GTK_TEXT_VIEW( text_widget ), &text_iter, 0, TRUE, 0, 0);
854                 }
855
856 #if GDEF_OS_WINDOWS
857                 gtk_widget_queue_draw( text_widget );
858 #endif
859
860                 text_buffer_ = text_buffer;
861                 free( buf );
862                 fclose( f );
863         }
864 }
865
866 // =============================================================================
867 // Light Intensity dialog
868
869 EMessageBoxReturn DoLightIntensityDlg( int *intensity ){
870         ModalDialog dialog;
871         ui::Entry intensity_entry{ui::null};
872         ModalDialogButton ok_button( dialog, eIDOK );
873         ModalDialogButton cancel_button( dialog, eIDCANCEL );
874
875         ui::Window window = MainFrame_getWindow().create_modal_dialog_window("Light intensity", dialog, -1, -1 );
876
877         auto accel_group = ui::AccelGroup(ui::New);
878         window.add_accel_group( accel_group );
879
880         {
881                 auto hbox = create_dialog_hbox( 4, 4 );
882                 window.add(hbox);
883                 {
884             auto vbox = create_dialog_vbox( 4 );
885                         hbox.pack_start( vbox, TRUE, TRUE, 0 );
886                         {
887                                 auto label = ui::Label( "ESC for default, ENTER to validate" );
888                                 label.show();
889                                 vbox.pack_start( label, FALSE, FALSE, 0 );
890                         }
891                         {
892                                 auto entry = ui::Entry(ui::New);
893                                 entry.show();
894                                 vbox.pack_start( entry, TRUE, TRUE, 0 );
895
896                                 gtk_widget_grab_focus( entry  );
897
898                                 intensity_entry = entry;
899                         }
900                 }
901                 {
902             auto vbox = create_dialog_vbox( 4 );
903                         hbox.pack_start( vbox, FALSE, FALSE, 0 );
904
905                         {
906                                 auto button = create_modal_dialog_button( "OK", ok_button );
907                                 vbox.pack_start( button, FALSE, FALSE, 0 );
908                                 widget_make_default( button );
909                                 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
910                         }
911                         {
912                 auto button = create_modal_dialog_button( "Cancel", cancel_button );
913                                 vbox.pack_start( button, FALSE, FALSE, 0 );
914                                 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
915                         }
916                 }
917         }
918
919         char buf[16];
920         sprintf( buf, "%d", *intensity );
921         intensity_entry.text(buf);
922
923         EMessageBoxReturn ret = modal_dialog_show( window, dialog );
924         if ( ret == eIDOK ) {
925                 *intensity = atoi( gtk_entry_get_text( intensity_entry ) );
926         }
927
928         window.destroy();
929
930         return ret;
931 }
932
933 // =============================================================================
934 // Add new shader tag dialog
935
936 EMessageBoxReturn DoShaderTagDlg( CopiedString* tag, const char* title ){
937         ModalDialog dialog;
938         ModalDialogButton ok_button( dialog, eIDOK );
939         ModalDialogButton cancel_button( dialog, eIDCANCEL );
940
941         auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
942
943         auto accel_group = ui::AccelGroup(ui::New);
944         window.add_accel_group( accel_group );
945
946         auto textentry = ui::Entry(ui::New);
947         {
948                 auto hbox = create_dialog_hbox( 4, 4 );
949                 window.add(hbox);
950                 {
951             auto vbox = create_dialog_vbox( 4 );
952                         hbox.pack_start( vbox, TRUE, TRUE, 0 );
953                         {
954                                 //GtkLabel* label = GTK_LABEL(gtk_label_new("Enter one ore more tags separated by spaces"));
955                                 auto label = ui::Label( "ESC to cancel, ENTER to validate" );
956                                 label.show();
957                                 vbox.pack_start( label, FALSE, FALSE, 0 );
958                         }
959                         {
960                                 auto entry = textentry;
961                                 entry.show();
962                                 vbox.pack_start( entry, TRUE, TRUE, 0 );
963
964                                 gtk_widget_grab_focus( entry  );
965                         }
966                 }
967                 {
968             auto vbox = create_dialog_vbox( 4 );
969                         hbox.pack_start( vbox, FALSE, FALSE, 0 );
970
971                         {
972                                 auto button = create_modal_dialog_button( "OK", ok_button );
973                                 vbox.pack_start( button, FALSE, FALSE, 0 );
974                                 widget_make_default( button );
975                                 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
976                         }
977                         {
978                 auto button = create_modal_dialog_button( "Cancel", cancel_button );
979                                 vbox.pack_start( button, FALSE, FALSE, 0 );
980                                 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
981                         }
982                 }
983         }
984
985         EMessageBoxReturn ret = modal_dialog_show( window, dialog );
986         if ( ret == eIDOK ) {
987                 *tag = gtk_entry_get_text( textentry );
988         }
989
990         window.destroy();
991
992         return ret;
993 }
994
995 EMessageBoxReturn DoShaderInfoDlg( const char* name, const char* filename, const char* title ){
996         ModalDialog dialog;
997         ModalDialogButton ok_button( dialog, eIDOK );
998
999         auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
1000
1001         auto accel_group = ui::AccelGroup(ui::New);
1002         window.add_accel_group( accel_group );
1003
1004         {
1005                 auto hbox = create_dialog_hbox( 4, 4 );
1006                 window.add(hbox);
1007                 {
1008             auto vbox = create_dialog_vbox( 4 );
1009                         hbox.pack_start( vbox, FALSE, FALSE, 0 );
1010                         {
1011                                 auto label = ui::Label( "The selected shader" );
1012                                 label.show();
1013                                 vbox.pack_start( label, FALSE, FALSE, 0 );
1014                         }
1015                         {
1016                                 auto label = ui::Label( name );
1017                                 label.show();
1018                                 vbox.pack_start( label, FALSE, FALSE, 0 );
1019                         }
1020                         {
1021                                 auto label = ui::Label( "is located in file" );
1022                                 label.show();
1023                                 vbox.pack_start( label, FALSE, FALSE, 0 );
1024                         }
1025                         {
1026                                 auto label = ui::Label( filename );
1027                                 label.show();
1028                                 vbox.pack_start( label, FALSE, FALSE, 0 );
1029                         }
1030                         {
1031                                 auto button = create_modal_dialog_button( "OK", ok_button );
1032                                 vbox.pack_start( button, FALSE, FALSE, 0 );
1033                                 widget_make_default( button );
1034                                 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
1035                         }
1036                 }
1037         }
1038
1039         EMessageBoxReturn ret = modal_dialog_show( window, dialog );
1040
1041         window.destroy();
1042
1043         return ret;
1044 }
1045
1046
1047
1048 #if GDEF_OS_WINDOWS
1049 #include <gdk/gdkwin32.h>
1050 #endif
1051
1052 #if GDEF_OS_WINDOWS
1053 // use the file associations to open files instead of builtin Gtk editor
1054 bool g_TextEditor_useWin32Editor = false;
1055 #else
1056 // custom shader editor
1057 bool g_TextEditor_useCustomEditor = false;
1058 CopiedString g_TextEditor_editorCommand( "" );
1059 #endif
1060
1061 void DoTextEditor( const char* filename, int cursorpos, int length ){
1062 #if GDEF_OS_WINDOWS
1063         if ( g_TextEditor_useWin32Editor ) {
1064                 StringOutputStream path( 256 );
1065                 StringOutputStream modpath( 256 );
1066                 const char* gamename = GlobalRadiant().getGameName();
1067                 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1068                 const char* enginePath = GlobalRadiant().getEnginePath();
1069                 path << enginePath << basegame << '/' << filename;
1070                 modpath << enginePath << gamename << '/' << filename;
1071                 if ( file_exists( modpath.c_str() ) ){
1072                         globalOutputStream() << "opening file '" << modpath.c_str() << "' (line " << cursorpos << " info ignored)\n";
1073                         ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", modpath.c_str(), 0, 0, SW_SHOW );
1074                 }
1075                 else if ( file_exists( path.c_str() ) ){
1076                         globalOutputStream() << "opening file '" << path.c_str() << "' (line " << cursorpos << " info ignored)\n";
1077                         ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", path.c_str(), 0, 0, SW_SHOW );
1078                 }
1079                 else{
1080                         globalOutputStream() << "Failed to open '" << filename << "'\nOne sits in .pk3 most likely!\n";
1081                 }
1082                 return;
1083         }
1084         else{
1085                 StringOutputStream path( 256 );
1086                 StringOutputStream modpath( 256 );
1087                 const char* gamename = GlobalRadiant().getGameName();
1088                 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1089                 const char* enginePath = GlobalRadiant().getEnginePath();
1090                 path << enginePath << basegame << '/' << filename;
1091                 modpath << enginePath << gamename << '/' << filename;
1092                 if ( file_exists( modpath.c_str() ) ){
1093                         globalOutputStream() << "opening file '" << modpath.c_str() << "' (line " << cursorpos << " info ignored)\n";
1094                         DoGtkTextEditor( modpath.c_str(), cursorpos, length );
1095                 }
1096                 else if ( file_exists( path.c_str() ) ){
1097                         globalOutputStream() << "opening file '" << path.c_str() << "' (line " << cursorpos << " info ignored)\n";
1098                         DoGtkTextEditor( path.c_str(), cursorpos, length );
1099                 }
1100                 else{
1101                         globalOutputStream() << "Failed to open '" << filename << "'\nOne sits in .pk3 most likely!\n";
1102                 }
1103                 return;
1104         }
1105 #else
1106         // check if a custom editor is set
1107         if ( g_TextEditor_useCustomEditor && !g_TextEditor_editorCommand.empty() ) {
1108                 StringOutputStream strEditCommand( 256 );
1109                 strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << filename << "\"";
1110
1111                 globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n";
1112                 // note: linux does not return false if the command failed so it will assume success
1113                 if ( Q_Exec( 0, const_cast<char*>( strEditCommand.c_str() ), 0, true, false ) == false ) {
1114                         globalOutputStream() << "Failed to execute " << strEditCommand.c_str() << ", using default\n";
1115                 }
1116                 else
1117                 {
1118                         // the command (appeared) to run successfully, no need to do anything more
1119                         return;
1120                 }
1121         }
1122
1123         DoGtkTextEditor( filename, cursorpos, length );
1124 #endif
1125 }