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