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