]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/gtkdlgs.cpp
f39487a535f3f543ea584e0225dd1a34e34020c9
[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         ui::Entry 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         combo->fsgame_entry.text( 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         dialog.game_combo.fsgame_entry.text( 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                                                 text_extensions.text(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)));
573                                                 gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW( text_extensions ), GTK_WRAP_WORD );
574                                                 text_extensions.show();
575                                         }
576                                 }
577                         }
578                 }
579         }
580
581         modal_dialog_show( window, dialog );
582
583         gtk_widget_destroy( GTK_WIDGET( window ) );
584 }
585
586 // =============================================================================
587 // TextureLayout dialog
588
589 // Last used texture scale values
590 static float last_used_texture_layout_scale_x = 4.0;
591 static float last_used_texture_layout_scale_y = 4.0;
592
593 EMessageBoxReturn DoTextureLayout( float *fx, float *fy ){
594         ModalDialog dialog;
595         ModalDialogButton ok_button( dialog, eIDOK );
596         ModalDialogButton cancel_button( dialog, eIDCANCEL );
597         ui::Entry x;
598         ui::Entry y;
599
600         auto window = MainFrame_getWindow().create_modal_dialog_window("Patch texture layout", dialog );
601
602         auto accel = ui::AccelGroup();
603         window.add_accel_group( accel );
604
605         {
606                 auto hbox = create_dialog_hbox( 4, 4 );
607                 window.add(hbox);
608                 {
609                         GtkVBox* vbox = create_dialog_vbox( 4 );
610                         gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), TRUE, TRUE, 0 );
611                         {
612                                 auto label = ui::Label( "Texture will be fit across the patch based\n"
613                                                                                                                         "on the x and y values given. Values of 1x1\n"
614                                                                                                                         "will \"fit\" the texture. 2x2 will repeat\n"
615                                                                                                                         "it twice, etc." );
616                                 label.show();
617                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), TRUE, TRUE, 0 );
618                                 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
619                         }
620                         {
621                                 auto table = create_dialog_table( 2, 2, 4, 4 );
622                                 table.show();
623                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
624                                 {
625                                         auto label = ui::Label( "Texture x:" );
626                                         label.show();
627                                         gtk_table_attach( table, GTK_WIDGET( label ), 0, 1, 0, 1,
628                                                                           (GtkAttachOptions) ( GTK_FILL ),
629                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
630                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
631                                 }
632                                 {
633                                         auto label = ui::Label( "Texture y:" );
634                                         label.show();
635                                         gtk_table_attach( table, GTK_WIDGET( label ), 0, 1, 1, 2,
636                                                                           (GtkAttachOptions) ( GTK_FILL ),
637                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
638                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
639                                 }
640                                 {
641                                         auto entry = ui::Entry();
642                                         entry.show();
643                                         gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 0, 1,
644                                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
645                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
646
647                                         x = entry;
648                                 }
649                                 {
650                                         auto entry = ui::Entry();
651                                         entry.show();
652                                         gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 1, 2,
653                                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
654                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
655
656                                         y = entry;
657                                 }
658                         }
659                 }
660                 {
661                         GtkVBox* vbox = create_dialog_vbox( 4 );
662                         gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), FALSE, FALSE, 0 );
663                         {
664                                 auto button = create_modal_dialog_button( "OK", ok_button );
665                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
666                                 widget_make_default( button );
667                                 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
668                         }
669                         {
670                                 GtkButton* button = create_modal_dialog_button( "Cancel", cancel_button );
671                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
672                                 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
673                         }
674                 }
675         }
676         
677         // Initialize with last used values
678         char buf[16];
679         
680         sprintf( buf, "%f", last_used_texture_layout_scale_x );
681         x.text( buf );
682         
683         sprintf( buf, "%f", last_used_texture_layout_scale_y );
684         y.text( buf );
685
686         // Set focus after intializing the values
687         gtk_widget_grab_focus( GTK_WIDGET( x ) );
688
689         EMessageBoxReturn ret = modal_dialog_show( window, dialog );
690         if ( ret == eIDOK ) {
691                 *fx = static_cast<float>( atof( gtk_entry_get_text( x ) ) );
692                 *fy = static_cast<float>( atof( gtk_entry_get_text( y ) ) );
693         
694                 // Remember last used values
695                 last_used_texture_layout_scale_x = *fx;
696                 last_used_texture_layout_scale_y = *fy;
697         }
698
699         gtk_widget_destroy( GTK_WIDGET( window ) );
700
701         return ret;
702 }
703
704 // =============================================================================
705 // Text Editor dialog
706
707 // master window widget
708 static ui::Widget text_editor;
709 static ui::Widget text_widget; // slave, text widget from the gtk editor
710
711 static gint editor_delete( ui::Widget widget, gpointer data ){
712         if ( widget.alert( "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
713                 return TRUE;
714         }
715
716         gtk_widget_hide( text_editor );
717
718         return TRUE;
719 }
720
721 static void editor_save( ui::Widget widget, gpointer data ){
722         FILE *f = fopen( (char*)g_object_get_data( G_OBJECT( data ), "filename" ), "w" );
723         gpointer text = g_object_get_data( G_OBJECT( data ), "text" );
724
725         if ( f == 0 ) {
726                 ui::Widget(GTK_WIDGET( data )).alert( "Error saving file !" );
727                 return;
728         }
729
730         char *str = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
731         fwrite( str, 1, strlen( str ), f );
732         fclose( f );
733 }
734
735 static void editor_close( ui::Widget widget, gpointer data ){
736         if ( text_editor.alert( "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
737                 return;
738         }
739
740         gtk_widget_hide( text_editor );
741 }
742
743 static void CreateGtkTextEditor(){
744         ui::Widget vbox, hbox, button, text;
745
746         auto dlg = ui::Window( ui::window_type::TOP );
747
748         dlg.connect( "delete_event",
749                                           G_CALLBACK( editor_delete ), 0 );
750         gtk_window_set_default_size( GTK_WINDOW( dlg ), 600, 300 );
751
752         vbox = ui::VBox( FALSE, 5 );
753         vbox.show();
754         dlg.add(vbox);
755         gtk_container_set_border_width( GTK_CONTAINER( vbox ), 5 );
756
757         auto scr = ui::ScrolledWindow();
758         scr.show();
759         gtk_box_pack_start( GTK_BOX( vbox ), scr, TRUE, TRUE, 0 );
760         gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scr ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
761         gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( scr ), GTK_SHADOW_IN );
762
763         text = ui::TextView();
764         scr.add(text);
765         text.show();
766         g_object_set_data( G_OBJECT( dlg ), "text", (gpointer) text );
767         gtk_text_view_set_editable( GTK_TEXT_VIEW( text ), TRUE );
768
769         hbox = ui::HBox( FALSE, 5 );
770         hbox.show();
771         gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), FALSE, TRUE, 0 );
772
773         button = ui::Button( "Close" );
774         button.show();
775         gtk_box_pack_end( GTK_BOX( hbox ), button, FALSE, FALSE, 0 );
776         button.connect( "clicked",
777                                           G_CALLBACK( editor_close ), dlg );
778         gtk_widget_set_size_request( button, 60, -1 );
779
780         button = ui::Button( "Save" );
781         button.show();
782         gtk_box_pack_end( GTK_BOX( hbox ), button, FALSE, FALSE, 0 );
783         button.connect( "clicked",
784                                           G_CALLBACK( editor_save ), dlg );
785         gtk_widget_set_size_request( button, 60, -1 );
786
787         text_editor = dlg;
788         text_widget = text;
789 }
790
791 static void DoGtkTextEditor( const char* filename, guint cursorpos ){
792         if ( !text_editor ) {
793                 CreateGtkTextEditor(); // build it the first time we need it
794
795         }
796         // Load file
797         FILE *f = fopen( filename, "r" );
798
799         if ( f == 0 ) {
800                 globalOutputStream() << "Unable to load file " << filename << " in shader editor.\n";
801                 gtk_widget_hide( text_editor );
802         }
803         else
804         {
805                 fseek( f, 0, SEEK_END );
806                 int len = ftell( f );
807                 void *buf = malloc( len );
808                 void *old_filename;
809
810                 rewind( f );
811                 fread( buf, 1, len, f );
812
813                 gtk_window_set_title( GTK_WINDOW( text_editor ), filename );
814
815                 GtkTextBuffer* text_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW( text_widget ) );
816                 gtk_text_buffer_set_text( text_buffer, (char*)buf, len );
817
818                 old_filename = g_object_get_data( G_OBJECT( text_editor ), "filename" );
819                 if ( old_filename ) {
820                         free( old_filename );
821                 }
822                 g_object_set_data( G_OBJECT( text_editor ), "filename", strdup( filename ) );
823
824                 // trying to show later
825                 text_editor.show();
826
827 #ifdef WIN32
828                 ui::process();
829 #endif
830
831                 // only move the cursor if it's not exceeding the size..
832                 // NOTE: this is erroneous, cursorpos is the offset in bytes, not in characters
833                 // len is the max size in bytes, not in characters either, but the character count is below that limit..
834                 // thinking .. the difference between character count and byte count would be only because of CR/LF?
835                 {
836                         GtkTextIter text_iter;
837                         // character offset, not byte offset
838                         gtk_text_buffer_get_iter_at_offset( text_buffer, &text_iter, cursorpos );
839                         gtk_text_buffer_place_cursor( text_buffer, &text_iter );
840                 }
841
842 #ifdef WIN32
843                 gtk_widget_queue_draw( text_widget );
844 #endif
845
846                 free( buf );
847                 fclose( f );
848         }
849 }
850
851 // =============================================================================
852 // Light Intensity dialog
853
854 EMessageBoxReturn DoLightIntensityDlg( int *intensity ){
855         ModalDialog dialog;
856         ui::Entry intensity_entry;
857         ModalDialogButton ok_button( dialog, eIDOK );
858         ModalDialogButton cancel_button( dialog, eIDCANCEL );
859
860         ui::Window window = MainFrame_getWindow().create_modal_dialog_window("Light intensity", dialog, -1, -1 );
861
862         auto accel_group = ui::AccelGroup();
863         window.add_accel_group( accel_group );
864
865         {
866                 auto hbox = create_dialog_hbox( 4, 4 );
867                 window.add(hbox);
868                 {
869                         GtkVBox* vbox = create_dialog_vbox( 4 );
870                         gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), TRUE, TRUE, 0 );
871                         {
872                                 auto label = ui::Label( "ESC for default, ENTER to validate" );
873                                 label.show();
874                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
875                         }
876                         {
877                                 auto entry = ui::Entry();
878                                 entry.show();
879                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( entry ), TRUE, TRUE, 0 );
880
881                                 gtk_widget_grab_focus( GTK_WIDGET( entry ) );
882
883                                 intensity_entry = entry;
884                         }
885                 }
886                 {
887                         GtkVBox* vbox = create_dialog_vbox( 4 );
888                         gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), FALSE, FALSE, 0 );
889
890                         {
891                                 auto button = create_modal_dialog_button( "OK", ok_button );
892                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
893                                 widget_make_default( button );
894                                 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
895                         }
896                         {
897                                 GtkButton* button = create_modal_dialog_button( "Cancel", cancel_button );
898                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
899                                 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
900                         }
901                 }
902         }
903
904         char buf[16];
905         sprintf( buf, "%d", *intensity );
906         intensity_entry.text(buf);
907
908         EMessageBoxReturn ret = modal_dialog_show( window, dialog );
909         if ( ret == eIDOK ) {
910                 *intensity = atoi( gtk_entry_get_text( intensity_entry ) );
911         }
912
913         gtk_widget_destroy( GTK_WIDGET( window ) );
914
915         return ret;
916 }
917
918 // =============================================================================
919 // Add new shader tag dialog
920
921 EMessageBoxReturn DoShaderTagDlg( CopiedString* tag, const char* title ){
922         ModalDialog dialog;
923         GtkEntry* textentry;
924         ModalDialogButton ok_button( dialog, eIDOK );
925         ModalDialogButton cancel_button( dialog, eIDCANCEL );
926
927         auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
928
929         auto accel_group = ui::AccelGroup();
930         window.add_accel_group( accel_group );
931
932         {
933                 auto hbox = create_dialog_hbox( 4, 4 );
934                 window.add(hbox);
935                 {
936                         GtkVBox* vbox = create_dialog_vbox( 4 );
937                         gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), TRUE, TRUE, 0 );
938                         {
939                                 //GtkLabel* label = GTK_LABEL(gtk_label_new("Enter one ore more tags separated by spaces"));
940                                 auto label = ui::Label( "ESC to cancel, ENTER to validate" );
941                                 label.show();
942                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
943                         }
944                         {
945                                 auto entry = ui::Entry();
946                                 entry.show();
947                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( entry ), TRUE, TRUE, 0 );
948
949                                 gtk_widget_grab_focus( GTK_WIDGET( entry ) );
950
951                                 textentry = entry;
952                         }
953                 }
954                 {
955                         GtkVBox* vbox = create_dialog_vbox( 4 );
956                         gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), FALSE, FALSE, 0 );
957
958                         {
959                                 auto button = create_modal_dialog_button( "OK", ok_button );
960                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
961                                 widget_make_default( button );
962                                 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
963                         }
964                         {
965                                 GtkButton* button = create_modal_dialog_button( "Cancel", cancel_button );
966                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
967                                 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
968                         }
969                 }
970         }
971
972         EMessageBoxReturn ret = modal_dialog_show( window, dialog );
973         if ( ret == eIDOK ) {
974                 *tag = gtk_entry_get_text( textentry );
975         }
976
977         gtk_widget_destroy( GTK_WIDGET( window ) );
978
979         return ret;
980 }
981
982 EMessageBoxReturn DoShaderInfoDlg( const char* name, const char* filename, const char* title ){
983         ModalDialog dialog;
984         ModalDialogButton ok_button( dialog, eIDOK );
985
986         auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
987
988         auto accel_group = ui::AccelGroup();
989         window.add_accel_group( accel_group );
990
991         {
992                 auto hbox = create_dialog_hbox( 4, 4 );
993                 window.add(hbox);
994                 {
995                         GtkVBox* vbox = create_dialog_vbox( 4 );
996                         gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), FALSE, FALSE, 0 );
997                         {
998                                 auto label = ui::Label( "The selected shader" );
999                                 label.show();
1000                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
1001                         }
1002                         {
1003                                 auto label = ui::Label( name );
1004                                 label.show();
1005                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
1006                         }
1007                         {
1008                                 auto label = ui::Label( "is located in file" );
1009                                 label.show();
1010                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
1011                         }
1012                         {
1013                                 auto label = ui::Label( filename );
1014                                 label.show();
1015                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
1016                         }
1017                         {
1018                                 auto button = create_modal_dialog_button( "OK", ok_button );
1019                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
1020                                 widget_make_default( button );
1021                                 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
1022                         }
1023                 }
1024         }
1025
1026         EMessageBoxReturn ret = modal_dialog_show( window, dialog );
1027
1028         gtk_widget_destroy( GTK_WIDGET( window ) );
1029
1030         return ret;
1031 }
1032
1033
1034
1035 #ifdef WIN32
1036 #include <gdk/gdkwin32.h>
1037 #endif
1038
1039 #ifdef WIN32
1040 // use the file associations to open files instead of builtin Gtk editor
1041 bool g_TextEditor_useWin32Editor = true;
1042 #else
1043 // custom shader editor
1044 bool g_TextEditor_useCustomEditor = false;
1045 CopiedString g_TextEditor_editorCommand( "" );
1046 #endif
1047
1048 void DoTextEditor( const char* filename, int cursorpos ){
1049 #ifdef WIN32
1050         if ( g_TextEditor_useWin32Editor ) {
1051                 globalOutputStream() << "opening file '" << filename << "' (line " << cursorpos << " info ignored)\n";
1052                 ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", filename, 0, 0, SW_SHOW );
1053                 return;
1054         }
1055 #else
1056         // check if a custom editor is set
1057         if ( g_TextEditor_useCustomEditor && !g_TextEditor_editorCommand.empty() ) {
1058                 StringOutputStream strEditCommand( 256 );
1059                 strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << filename << "\"";
1060
1061                 globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n";
1062                 // note: linux does not return false if the command failed so it will assume success
1063                 if ( Q_Exec( 0, const_cast<char*>( strEditCommand.c_str() ), 0, true, false ) == false ) {
1064                         globalOutputStream() << "Failed to execute " << strEditCommand.c_str() << ", using default\n";
1065                 }
1066                 else
1067                 {
1068                         // the command (appeared) to run successfully, no need to do anything more
1069                         return;
1070                 }
1071         }
1072 #endif
1073
1074         DoGtkTextEditor( filename, cursorpos );
1075 }