]> git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/gtkdlgs.cpp
Merge commit '8d6828cd68e9104c2c68b962b341b4f572b4bc38' 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                                         gtk_widget_set_sensitive( GTK_WIDGET( button ), FALSE);
545                                 }
546                                 {
547                     auto button = create_dialog_button( "Changes", G_CALLBACK( about_button_changelog ), 0 );
548                                         vbox2.pack_start( button, FALSE, FALSE, 0 );
549                                 }
550                                 {
551                     auto button = create_dialog_button( "Issues", G_CALLBACK( about_button_issues ), 0 );
552                                         vbox2.pack_start( button, FALSE, FALSE, 0 );
553                                         gtk_widget_set_sensitive( GTK_WIDGET( button ), FALSE);
554                                 }
555                         }
556                 }
557                 {
558                         auto frame = create_dialog_frame( "OpenGL Properties" );
559                         vbox.pack_start( frame, FALSE, FALSE, 0 );
560                         {
561                                 auto table = create_dialog_table( 3, 2, 4, 4, 4 );
562                                 frame.add(table);
563                                 {
564                                         auto label = ui::Label( "Vendor:" );
565                                         label.show();
566                     table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
567                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
568                                 }
569                                 {
570                                         auto label = ui::Label( "Version:" );
571                                         label.show();
572                     table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
573                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
574                                 }
575                                 {
576                                         auto label = ui::Label( "Renderer:" );
577                                         label.show();
578                     table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0});
579                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
580                                 }
581                                 {
582                                         auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VENDOR ) ) );
583                                         label.show();
584                     table.attach(label, {1, 2, 0, 1}, {GTK_FILL, 0});
585                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
586                                 }
587                                 {
588                                         auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VERSION ) ) );
589                                         label.show();
590                     table.attach(label, {1, 2, 1, 2}, {GTK_FILL, 0});
591                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
592                                 }
593                                 {
594                                         auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_RENDERER ) ) );
595                                         label.show();
596                     table.attach(label, {1, 2, 2, 3}, {GTK_FILL, 0});
597                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
598                                 }
599                         }
600                         {
601                                 auto frame = create_dialog_frame( "OpenGL Extensions" );
602                                 vbox.pack_start( frame, TRUE, TRUE, 0 );
603                                 {
604                                         auto sc_extensions = create_scrolled_window( ui::Policy::AUTOMATIC, ui::Policy::ALWAYS, 4 );
605                                         frame.add(sc_extensions);
606                                         {
607                                                 auto text_extensions = ui::TextView(ui::New);
608                                                 gtk_text_view_set_editable( text_extensions, FALSE );
609                                                 sc_extensions.add(text_extensions);
610                                                 text_extensions.text(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)));
611                                                 gtk_text_view_set_wrap_mode( text_extensions, GTK_WRAP_WORD );
612                                                 text_extensions.show();
613                                         }
614                                 }
615                         }
616                 }
617         }
618
619         modal_dialog_show( window, dialog );
620
621         window.destroy();
622 }
623
624 // =============================================================================
625 // TextureLayout dialog
626
627 // Last used texture scale values
628 static float last_used_texture_layout_scale_x = 4.0;
629 static float last_used_texture_layout_scale_y = 4.0;
630
631 EMessageBoxReturn DoTextureLayout( float *fx, float *fy ){
632         ModalDialog dialog;
633         ModalDialogButton ok_button( dialog, eIDOK );
634         ModalDialogButton cancel_button( dialog, eIDCANCEL );
635         ui::Entry x{ui::null};
636         ui::Entry y{ui::null};
637
638         auto window = MainFrame_getWindow().create_modal_dialog_window("Patch texture layout", dialog );
639
640         auto accel = ui::AccelGroup(ui::New);
641         window.add_accel_group( accel );
642
643         {
644                 auto hbox = create_dialog_hbox( 4, 4 );
645                 window.add(hbox);
646                 {
647             auto vbox = create_dialog_vbox( 4 );
648                         hbox.pack_start( vbox, TRUE, TRUE, 0 );
649                         {
650                                 auto label = ui::Label( "Texture will be fit across the patch based\n"
651                                                                                                                         "on the x and y values given. Values of 1x1\n"
652                                                                                                                         "will \"fit\" the texture. 2x2 will repeat\n"
653                                                                                                                         "it twice, etc." );
654                                 label.show();
655                                 vbox.pack_start( label, TRUE, TRUE, 0 );
656                                 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
657                         }
658                         {
659                                 auto table = create_dialog_table( 2, 2, 4, 4 );
660                                 table.show();
661                                 vbox.pack_start( table, TRUE, TRUE, 0 );
662                                 {
663                                         auto label = ui::Label( "Texture x:" );
664                                         label.show();
665                     table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
666                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
667                                 }
668                                 {
669                                         auto label = ui::Label( "Texture y:" );
670                                         label.show();
671                     table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
672                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
673                                 }
674                                 {
675                                         auto entry = ui::Entry(ui::New);
676                                         entry.show();
677                     table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
678
679                                         x = entry;
680                                 }
681                                 {
682                                         auto entry = ui::Entry(ui::New);
683                                         entry.show();
684                     table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
685
686                                         y = entry;
687                                 }
688                         }
689                 }
690                 {
691             auto vbox = create_dialog_vbox( 4 );
692                         hbox.pack_start( vbox, FALSE, FALSE, 0 );
693                         {
694                                 auto button = create_modal_dialog_button( "OK", ok_button );
695                                 vbox.pack_start( button, FALSE, FALSE, 0 );
696                                 widget_make_default( button );
697                                 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
698                         }
699                         {
700                 auto button = create_modal_dialog_button( "Cancel", cancel_button );
701                                 vbox.pack_start( button, FALSE, FALSE, 0 );
702                                 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
703                         }
704                 }
705         }
706
707         // Initialize with last used values
708         char buf[16];
709
710         sprintf( buf, "%f", last_used_texture_layout_scale_x );
711         x.text( buf );
712
713         sprintf( buf, "%f", last_used_texture_layout_scale_y );
714         y.text( buf );
715
716         // Set focus after intializing the values
717         gtk_widget_grab_focus( x  );
718
719         EMessageBoxReturn ret = modal_dialog_show( window, dialog );
720         if ( ret == eIDOK ) {
721                 *fx = static_cast<float>( atof( gtk_entry_get_text( x ) ) );
722                 *fy = static_cast<float>( atof( gtk_entry_get_text( y ) ) );
723
724                 // Remember last used values
725                 last_used_texture_layout_scale_x = *fx;
726                 last_used_texture_layout_scale_y = *fy;
727         }
728
729         window.destroy();
730
731         return ret;
732 }
733
734 // =============================================================================
735 // Text Editor dialog
736
737 // master window widget
738 static ui::Window text_editor{ui::null};
739 static ui::Widget text_widget{ui::null}; // slave, text widget from the gtk editor
740 static GtkTextBuffer* text_buffer_;
741
742 static gint editor_delete( ui::Widget widget, gpointer data ){
743 /*      if ( ui::alert( widget.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
744                 return TRUE;
745         }
746 */
747         text_editor.hide();
748
749         return TRUE;
750 }
751
752 static void editor_save( ui::Widget widget, gpointer data ){
753         FILE *f = fopen( (char*)g_object_get_data( G_OBJECT( data ), "filename" ), "w" );
754         //gpointer text = g_object_get_data( G_OBJECT( data ), "text" );
755
756         if ( f == 0 ) {
757                 ui::alert( ui::Widget::from(data).window(), "Error saving file !" );
758                 return;
759         }
760
761         /* Obtain iters for the start and end of points of the buffer */
762         GtkTextIter start;
763         GtkTextIter end;
764         gtk_text_buffer_get_start_iter (text_buffer_, &start);
765         gtk_text_buffer_get_end_iter (text_buffer_, &end);
766
767         /* Get the entire buffer text. */
768         char *str = gtk_text_buffer_get_text (text_buffer_, &start, &end, FALSE);
769
770         //char *str = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
771         fwrite( str, 1, strlen( str ), f );
772         fclose( f );
773         g_free (str);
774 }
775
776 static void editor_close( ui::Widget widget, gpointer data ){
777 /*      if ( ui::alert( text_editor.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
778                 return;
779         }
780 */
781         text_editor.hide();
782 }
783
784 static void CreateGtkTextEditor(){
785         auto dlg = ui::Window( ui::window_type::TOP );
786
787         dlg.connect( "", G_CALLBACK( editor_delete ), 0 );
788         gtk_window_set_default_size( dlg, 400, 600 );
789
790         auto vbox = ui::VBox( FALSE, 5 );
791         vbox.show();
792         dlg.add(vbox);
793         gtk_container_set_border_width( GTK_CONTAINER( vbox ), 5 );
794
795         auto scr = ui::ScrolledWindow(ui::New);
796         scr.show();
797         vbox.pack_start( scr, TRUE, TRUE, 0 );
798         gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scr ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
799         gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( scr ), GTK_SHADOW_IN );
800
801         auto text = ui::TextView(ui::New);
802         scr.add(text);
803         text.show();
804         g_object_set_data( G_OBJECT( dlg ), "text", (gpointer) text );
805         gtk_text_view_set_editable( text, TRUE );
806
807         auto hbox = ui::HBox( FALSE, 5 );
808         hbox.show();
809         vbox.pack_start( hbox, FALSE, TRUE, 0 );
810
811         auto button = ui::Button( "Close" );
812         button.show();
813         hbox.pack_end(button, FALSE, FALSE, 0);
814         button.connect( "clicked",
815                                           G_CALLBACK( editor_close ), dlg );
816         button.dimensions(60, -1);
817
818         button = ui::Button( "Save" );
819         button.show();
820         hbox.pack_end(button, FALSE, FALSE, 0);
821         button.connect( "clicked",
822                                           G_CALLBACK( editor_save ), dlg );
823         button.dimensions(60, -1);
824
825         text_editor = dlg;
826         text_widget = text;
827 }
828
829 static void DoGtkTextEditor( const char* filename, guint cursorpos, int length ){
830         if ( !text_editor ) {
831                 CreateGtkTextEditor(); // build it the first time we need it
832
833         }
834         // Load file
835         FILE *f = fopen( filename, "r" );
836
837         if ( f == 0 ) {
838                 globalOutputStream() << "Unable to load file " << filename << " in shader editor.\n";
839                 text_editor.hide();
840         }
841         else
842         {
843                 fseek( f, 0, SEEK_END );
844                 int len = ftell( f );
845                 void *buf = malloc( len );
846                 void *old_filename;
847
848                 rewind( f );
849                 fread( buf, 1, len, f );
850
851                 gtk_window_set_title( text_editor, filename );
852
853                 auto text_buffer = gtk_text_view_get_buffer(ui::TextView::from(text_widget));
854                 gtk_text_buffer_set_text( text_buffer, (char*)buf, length );
855
856                 old_filename = g_object_get_data( G_OBJECT( text_editor ), "filename" );
857                 if ( old_filename ) {
858                         free( old_filename );
859                 }
860                 g_object_set_data( G_OBJECT( text_editor ), "filename", strdup( filename ) );
861
862                 // trying to show later
863                 text_editor.show();
864                 gtk_window_present( GTK_WINDOW( text_editor ) );
865
866 //#if GDEF_OS_WINDOWS
867                 ui::process();
868 //#endif
869
870                 // only move the cursor if it's not exceeding the size..
871                 // NOTE: this is erroneous, cursorpos is the offset in bytes, not in characters
872                 // len is the max size in bytes, not in characters either, but the character count is below that limit..
873                 // thinking .. the difference between character count and byte count would be only because of CR/LF?
874                 {
875                         GtkTextIter text_iter;
876                         // character offset, not byte offset
877                         gtk_text_buffer_get_iter_at_offset( text_buffer, &text_iter, cursorpos );
878                         gtk_text_buffer_place_cursor( text_buffer, &text_iter );
879                         gtk_text_view_scroll_to_iter( GTK_TEXT_VIEW( text_widget ), &text_iter, 0, TRUE, 0, 0);
880                 }
881
882 //#if GDEF_OS_WINDOWS
883                 gtk_widget_queue_draw( text_widget );
884 //#endif
885
886                 text_buffer_ = text_buffer;
887                 free( buf );
888                 fclose( f );
889         }
890 }
891
892 // =============================================================================
893 // Light Intensity dialog
894
895 EMessageBoxReturn DoLightIntensityDlg( int *intensity ){
896         ModalDialog dialog;
897         ui::Entry intensity_entry{ui::null};
898         ModalDialogButton ok_button( dialog, eIDOK );
899         ModalDialogButton cancel_button( dialog, eIDCANCEL );
900
901         ui::Window window = MainFrame_getWindow().create_modal_dialog_window("Light intensity", dialog, -1, -1 );
902
903         auto accel_group = ui::AccelGroup(ui::New);
904         window.add_accel_group( accel_group );
905
906         {
907                 auto hbox = create_dialog_hbox( 4, 4 );
908                 window.add(hbox);
909                 {
910             auto vbox = create_dialog_vbox( 4 );
911                         hbox.pack_start( vbox, TRUE, TRUE, 0 );
912                         {
913                                 auto label = ui::Label( "ESC for default, ENTER to validate" );
914                                 label.show();
915                                 vbox.pack_start( label, FALSE, FALSE, 0 );
916                         }
917                         {
918                                 auto entry = ui::Entry(ui::New);
919                                 entry.show();
920                                 vbox.pack_start( entry, TRUE, TRUE, 0 );
921
922                                 gtk_widget_grab_focus( entry  );
923
924                                 intensity_entry = entry;
925                         }
926                 }
927                 {
928             auto vbox = create_dialog_vbox( 4 );
929                         hbox.pack_start( vbox, FALSE, FALSE, 0 );
930
931                         {
932                                 auto button = create_modal_dialog_button( "OK", ok_button );
933                                 vbox.pack_start( button, FALSE, FALSE, 0 );
934                                 widget_make_default( button );
935                                 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
936                         }
937                         {
938                 auto button = create_modal_dialog_button( "Cancel", cancel_button );
939                                 vbox.pack_start( button, FALSE, FALSE, 0 );
940                                 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
941                         }
942                 }
943         }
944
945         char buf[16];
946         sprintf( buf, "%d", *intensity );
947         intensity_entry.text(buf);
948
949         EMessageBoxReturn ret = modal_dialog_show( window, dialog );
950         if ( ret == eIDOK ) {
951                 *intensity = atoi( gtk_entry_get_text( intensity_entry ) );
952         }
953
954         window.destroy();
955
956         return ret;
957 }
958
959 // =============================================================================
960 // Add new shader tag dialog
961
962 EMessageBoxReturn DoShaderTagDlg( CopiedString* tag, const char* title ){
963         ModalDialog dialog;
964         ModalDialogButton ok_button( dialog, eIDOK );
965         ModalDialogButton cancel_button( dialog, eIDCANCEL );
966
967         auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
968
969         auto accel_group = ui::AccelGroup(ui::New);
970         window.add_accel_group( accel_group );
971
972         auto textentry = ui::Entry(ui::New);
973         {
974                 auto hbox = create_dialog_hbox( 4, 4 );
975                 window.add(hbox);
976                 {
977             auto vbox = create_dialog_vbox( 4 );
978                         hbox.pack_start( vbox, TRUE, TRUE, 0 );
979                         {
980                                 //GtkLabel* label = GTK_LABEL(gtk_label_new("Enter one ore more tags separated by spaces"));
981                                 auto label = ui::Label( "ESC to cancel, ENTER to validate" );
982                                 label.show();
983                                 vbox.pack_start( label, FALSE, FALSE, 0 );
984                         }
985                         {
986                                 auto entry = textentry;
987                                 entry.show();
988                                 vbox.pack_start( entry, TRUE, TRUE, 0 );
989
990                                 gtk_widget_grab_focus( entry  );
991                         }
992                 }
993                 {
994             auto vbox = create_dialog_vbox( 4 );
995                         hbox.pack_start( vbox, FALSE, FALSE, 0 );
996
997                         {
998                                 auto button = create_modal_dialog_button( "OK", ok_button );
999                                 vbox.pack_start( button, FALSE, FALSE, 0 );
1000                                 widget_make_default( button );
1001                                 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
1002                         }
1003                         {
1004                 auto button = create_modal_dialog_button( "Cancel", cancel_button );
1005                                 vbox.pack_start( button, FALSE, FALSE, 0 );
1006                                 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
1007                         }
1008                 }
1009         }
1010
1011         EMessageBoxReturn ret = modal_dialog_show( window, dialog );
1012         if ( ret == eIDOK ) {
1013                 *tag = gtk_entry_get_text( textentry );
1014         }
1015
1016         window.destroy();
1017
1018         return ret;
1019 }
1020
1021 EMessageBoxReturn DoShaderInfoDlg( const char* name, const char* filename, const char* title ){
1022         ModalDialog dialog;
1023         ModalDialogButton ok_button( dialog, eIDOK );
1024
1025         auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
1026
1027         auto accel_group = ui::AccelGroup(ui::New);
1028         window.add_accel_group( accel_group );
1029
1030         {
1031                 auto hbox = create_dialog_hbox( 4, 4 );
1032                 window.add(hbox);
1033                 {
1034             auto vbox = create_dialog_vbox( 4 );
1035                         hbox.pack_start( vbox, FALSE, FALSE, 0 );
1036                         {
1037                                 auto label = ui::Label( "The selected shader" );
1038                                 label.show();
1039                                 vbox.pack_start( label, FALSE, FALSE, 0 );
1040                         }
1041                         {
1042                                 auto label = ui::Label( name );
1043                                 label.show();
1044                                 vbox.pack_start( label, FALSE, FALSE, 0 );
1045                         }
1046                         {
1047                                 auto label = ui::Label( "is located in file" );
1048                                 label.show();
1049                                 vbox.pack_start( label, FALSE, FALSE, 0 );
1050                         }
1051                         {
1052                                 auto label = ui::Label( filename );
1053                                 label.show();
1054                                 vbox.pack_start( label, FALSE, FALSE, 0 );
1055                         }
1056                         {
1057                                 auto button = create_modal_dialog_button( "OK", ok_button );
1058                                 vbox.pack_start( button, FALSE, FALSE, 0 );
1059                                 widget_make_default( button );
1060                                 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
1061                         }
1062                 }
1063         }
1064
1065         EMessageBoxReturn ret = modal_dialog_show( window, dialog );
1066
1067         window.destroy();
1068
1069         return ret;
1070 }
1071
1072
1073
1074 #if GDEF_OS_WINDOWS
1075 #include <gdk/gdkwin32.h>
1076 #endif
1077
1078 CopiedString g_TextEditor_editorCommand( "" );
1079
1080 void DoTextEditor( const char* filename, int cursorpos, int length, bool external_editor ){
1081         //StringOutputStream paths[4]( 256 );
1082         StringOutputStream paths[4] = { StringOutputStream(256) };
1083         StringOutputStream* goodpath = 0;
1084
1085         const char* gamename = GlobalRadiant().getGameName();
1086         const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1087         const char* enginePath = GlobalRadiant().getEnginePath();
1088         const char* homeEnginePath = g_qeglobals.m_userEnginePath.c_str();
1089
1090         paths[0] << homeEnginePath << gamename << '/' << filename;
1091         paths[1] << enginePath << gamename << '/' << filename;
1092         paths[2] << homeEnginePath << basegame << '/' << filename;
1093         paths[3] << enginePath << basegame << '/' << filename;
1094
1095         for ( std::size_t i = 0; i < 4; ++i ){
1096                 if ( file_exists( paths[i].c_str() ) ){
1097                         goodpath = &paths[i];
1098                         break;
1099                 }
1100         }
1101
1102         if( goodpath ){
1103                 globalOutputStream() << "opening file '" << goodpath->c_str() << "' (line " << cursorpos << " info ignored)\n";
1104                 if( external_editor ){
1105                         if( g_TextEditor_editorCommand.empty() ){
1106 #ifdef WIN32
1107                                 ShellExecute( (HWND)GDK_WINDOW_HWND( GTK_WIDGET( MainFrame_getWindow() )->window ), 0, goodpath->c_str(), 0, 0, SW_SHOWNORMAL );
1108 //                              SHELLEXECUTEINFO ShExecInfo;
1109 //                              ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
1110 //                              ShExecInfo.fMask = 0;
1111 //                              ShExecInfo.hwnd = (HWND)GDK_WINDOW_HWND( GTK_WIDGET( MainFrame_getWindow() )->window );
1112 //                              ShExecInfo.lpVerb = NULL;
1113 //                              ShExecInfo.lpFile = goodpath->c_str();
1114 //                              ShExecInfo.lpParameters = NULL;
1115 //                              ShExecInfo.lpDirectory = NULL;
1116 //                              ShExecInfo.nShow = SW_SHOWNORMAL;
1117 //                              ShExecInfo.hInstApp = NULL;
1118 //                              ShellExecuteEx(&ShExecInfo);
1119 #else
1120                                 globalOutputStream() << "Failed to open '" << goodpath->c_str() << "'\nSet Shader Editor Command in preferences\n";
1121 #endif
1122                         }
1123                         else{
1124                                 StringOutputStream strEditCommand( 256 );
1125                                 strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << goodpath->c_str() << "\"";
1126
1127                                 globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n";
1128                                 // note: linux does not return false if the command failed so it will assume success
1129                                 if ( Q_Exec( 0, const_cast<char*>( strEditCommand.c_str() ), 0, true, false ) == false ) {
1130                                         globalOutputStream() << "Failed to execute " << strEditCommand.c_str() << "\n";
1131                                 }
1132                                 else
1133                                 {
1134                                         // the command (appeared) to run successfully, no need to do anything more
1135                                         return;
1136                                 }
1137                         }
1138                 }
1139                 else{
1140                         DoGtkTextEditor( goodpath->c_str(), cursorpos, length );
1141                 }
1142         }
1143         else{
1144                 globalOutputStream() << "Failed to open '" << filename << "'\nOne sits in .pk3 most likely!\n";
1145         }
1146 }