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