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