]> git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/gtkdlgs.cpp
build/about: reword a bit the about message
[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 static void AddParagraph( ui::VBox vbox, const char* text, bool use_markup ){
424         auto label = ui::Label( text );
425         gtk_label_set_use_markup( GTK_LABEL( label ), use_markup );
426         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0 );
427         gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
428         label.show();
429         vbox.pack_start( label, TRUE, TRUE, 0 );
430 }
431
432 static void AddParagraph( ui::VBox vbox, const char* text ){
433         AddParagraph( vbox, text, false );
434 }
435
436 void DoAbout(){
437         ModalDialog dialog;
438         ModalDialogButton ok_button( dialog, eIDOK );
439
440         auto window = MainFrame_getWindow().create_modal_dialog_window("About " RADIANT_NAME, dialog );
441
442         {
443                 auto vbox = create_dialog_vbox( 4, 4 );
444                 window.add(vbox);
445
446                 {
447             auto hbox = create_dialog_hbox( 4 );
448                         vbox.pack_start( hbox, FALSE, FALSE, 0 );
449
450                         {
451                 auto vbox2 = create_dialog_vbox( 4 );
452                                 hbox.pack_start( vbox2, FALSE, FALSE, 5 );
453                                 {
454                                         auto frame = create_dialog_frame( 0, ui::Shadow::IN );
455                                         vbox2.pack_start( frame, FALSE, FALSE, 0 );
456                                         {
457                                                 auto image = new_local_image( "logo.png" );
458                                                 image.show();
459                                                 frame.add(image);
460                                         }
461                                 }
462                         }
463
464                         {
465                                 // HACK: that may not be related to font size
466                                 auto about_vbox = ui::VBox( FALSE, 5 );
467                                 about_vbox.show();
468                                 hbox.pack_start( about_vbox, FALSE, FALSE, 0 );
469
470                                 AddParagraph( about_vbox,
471                                         RADIANT_NAME " " RADIANT_VERSION_STRING " (" __DATE__ ")\n"
472                                         RADIANT_ABOUTMSG );
473                                 AddParagraph( about_vbox,
474                                         "Get news and latest build at "
475                                         "<a href='https://netradiant.gitlab.io/'>"
476                                                 "netradiant.gitlab.io"
477                                         "</a>\n"
478                                         "Please report your issues at "
479                                         "<a href='https://gitlab.com/xonotic/netradiant/issues'>"
480                                                 "gitlab.com/xonotic/netradiant/issues"
481                                         "</a>\n"
482                                         "The team cannot provide support for custom builds.", true );
483                                 AddParagraph( about_vbox,
484                                         RADIANT_NAME " is a community project maintained by "
485                                         "<a href='https://xonotic.org'>"
486                                                 "Xonotic"
487                                         "</a>\n"
488                                         "and developed with help from "
489                                         "<a href='https://netradiant.gitlab.io/page/about/'>"
490                                                 "other game projects"
491                                         "</a> and individuals. ", true );
492                                 AddParagraph( about_vbox,
493                                         "This program is free software licensed under the GNU GPL." );
494                         }
495
496                         {
497                 auto vbox2 = create_dialog_vbox( 4 );
498                                 hbox.pack_start( vbox2, TRUE, TRUE, 0 );
499                                 {
500                     auto button = create_modal_dialog_button( "OK", ok_button );
501                                         vbox2.pack_start( button, FALSE, FALSE, 0 );
502                                 }
503                                 {
504                     auto button = create_dialog_button( "Credits", G_CALLBACK( about_button_credits ), 0 );
505                                         vbox2.pack_start( button, FALSE, FALSE, 0 );
506                                 }
507                                 {
508                     auto button = create_dialog_button( "Changes", G_CALLBACK( about_button_changelog ), 0 );
509                                         vbox2.pack_start( button, FALSE, FALSE, 0 );
510                                 }
511                                 {
512                     auto button = create_dialog_button( "Issues", G_CALLBACK( about_button_issues ), 0 );
513                                         vbox2.pack_start( button, FALSE, FALSE, 0 );
514                                 }
515                         }
516                 }
517                 {
518                         auto frame = create_dialog_frame( "OpenGL Properties" );
519                         vbox.pack_start( frame, FALSE, FALSE, 0 );
520                         {
521                                 auto table = create_dialog_table( 3, 2, 4, 4, 4 );
522                                 frame.add(table);
523                                 {
524                                         auto label = ui::Label( "Vendor:" );
525                                         label.show();
526                     table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
527                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
528                                 }
529                                 {
530                                         auto label = ui::Label( "Version:" );
531                                         label.show();
532                     table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
533                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
534                                 }
535                                 {
536                                         auto label = ui::Label( "Renderer:" );
537                                         label.show();
538                     table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0});
539                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
540                                 }
541                                 {
542                                         auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VENDOR ) ) );
543                                         label.show();
544                     table.attach(label, {1, 2, 0, 1}, {GTK_FILL, 0});
545                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
546                                 }
547                                 {
548                                         auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VERSION ) ) );
549                                         label.show();
550                     table.attach(label, {1, 2, 1, 2}, {GTK_FILL, 0});
551                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
552                                 }
553                                 {
554                                         auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_RENDERER ) ) );
555                                         label.show();
556                     table.attach(label, {1, 2, 2, 3}, {GTK_FILL, 0});
557                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
558                                 }
559                         }
560                         {
561                                 auto frame = create_dialog_frame( "OpenGL Extensions" );
562                                 vbox.pack_start( frame, TRUE, TRUE, 0 );
563                                 {
564                                         auto sc_extensions = create_scrolled_window( ui::Policy::AUTOMATIC, ui::Policy::ALWAYS, 4 );
565                                         frame.add(sc_extensions);
566                                         {
567                                                 auto text_extensions = ui::TextView(ui::New);
568                                                 gtk_text_view_set_editable( text_extensions, FALSE );
569                                                 sc_extensions.add(text_extensions);
570                                                 text_extensions.text(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)));
571                                                 gtk_text_view_set_wrap_mode( text_extensions, GTK_WRAP_WORD );
572                                                 text_extensions.show();
573                                         }
574                                 }
575                         }
576                 }
577         }
578
579         modal_dialog_show( window, dialog );
580
581         window.destroy();
582 }
583
584 // =============================================================================
585 // TextureLayout dialog
586
587 // Last used texture scale values
588 static float last_used_texture_layout_scale_x = 4.0;
589 static float last_used_texture_layout_scale_y = 4.0;
590
591 EMessageBoxReturn DoTextureLayout( float *fx, float *fy ){
592         ModalDialog dialog;
593         ModalDialogButton ok_button( dialog, eIDOK );
594         ModalDialogButton cancel_button( dialog, eIDCANCEL );
595         ui::Entry x{ui::null};
596         ui::Entry y{ui::null};
597
598         auto window = MainFrame_getWindow().create_modal_dialog_window("Patch texture layout", dialog );
599
600         auto accel = ui::AccelGroup(ui::New);
601         window.add_accel_group( accel );
602
603         {
604                 auto hbox = create_dialog_hbox( 4, 4 );
605                 window.add(hbox);
606                 {
607             auto vbox = create_dialog_vbox( 4 );
608                         hbox.pack_start( vbox, TRUE, TRUE, 0 );
609                         {
610                                 auto label = ui::Label( "Texture will be fit across the patch based\n"
611                                                                                                                         "on the x and y values given. Values of 1x1\n"
612                                                                                                                         "will \"fit\" the texture. 2x2 will repeat\n"
613                                                                                                                         "it twice, etc." );
614                                 label.show();
615                                 vbox.pack_start( label, TRUE, TRUE, 0 );
616                                 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
617                         }
618                         {
619                                 auto table = create_dialog_table( 2, 2, 4, 4 );
620                                 table.show();
621                                 vbox.pack_start( table, TRUE, TRUE, 0 );
622                                 {
623                                         auto label = ui::Label( "Texture x:" );
624                                         label.show();
625                     table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
626                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
627                                 }
628                                 {
629                                         auto label = ui::Label( "Texture y:" );
630                                         label.show();
631                     table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
632                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
633                                 }
634                                 {
635                                         auto entry = ui::Entry(ui::New);
636                                         entry.show();
637                     table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
638
639                                         x = entry;
640                                 }
641                                 {
642                                         auto entry = ui::Entry(ui::New);
643                                         entry.show();
644                     table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
645
646                                         y = entry;
647                                 }
648                         }
649                 }
650                 {
651             auto vbox = create_dialog_vbox( 4 );
652                         hbox.pack_start( vbox, FALSE, FALSE, 0 );
653                         {
654                                 auto button = create_modal_dialog_button( "OK", ok_button );
655                                 vbox.pack_start( button, FALSE, FALSE, 0 );
656                                 widget_make_default( button );
657                                 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
658                         }
659                         {
660                 auto button = create_modal_dialog_button( "Cancel", cancel_button );
661                                 vbox.pack_start( button, FALSE, FALSE, 0 );
662                                 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
663                         }
664                 }
665         }
666         
667         // Initialize with last used values
668         char buf[16];
669         
670         sprintf( buf, "%f", last_used_texture_layout_scale_x );
671         x.text( buf );
672         
673         sprintf( buf, "%f", last_used_texture_layout_scale_y );
674         y.text( buf );
675
676         // Set focus after intializing the values
677         gtk_widget_grab_focus( x  );
678
679         EMessageBoxReturn ret = modal_dialog_show( window, dialog );
680         if ( ret == eIDOK ) {
681                 *fx = static_cast<float>( atof( gtk_entry_get_text( x ) ) );
682                 *fy = static_cast<float>( atof( gtk_entry_get_text( y ) ) );
683         
684                 // Remember last used values
685                 last_used_texture_layout_scale_x = *fx;
686                 last_used_texture_layout_scale_y = *fy;
687         }
688
689         window.destroy();
690
691         return ret;
692 }
693
694 // =============================================================================
695 // Text Editor dialog
696
697 // master window widget
698 static ui::Window text_editor{ui::null};
699 static ui::Widget text_widget{ui::null}; // slave, text widget from the gtk editor
700
701 static gint editor_delete( ui::Widget widget, gpointer data ){
702         if ( ui::alert( widget.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
703                 return TRUE;
704         }
705
706         text_editor.hide();
707
708         return TRUE;
709 }
710
711 static void editor_save( ui::Widget widget, gpointer data ){
712         FILE *f = fopen( (char*)g_object_get_data( G_OBJECT( data ), "filename" ), "w" );
713         gpointer text = g_object_get_data( G_OBJECT( data ), "text" );
714
715         if ( f == 0 ) {
716                 ui::alert( ui::Widget::from(data).window(), "Error saving file !" );
717                 return;
718         }
719
720         char *str = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
721         fwrite( str, 1, strlen( str ), f );
722         fclose( f );
723 }
724
725 static void editor_close( ui::Widget widget, gpointer data ){
726         if ( ui::alert( text_editor.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
727                 return;
728         }
729
730         text_editor.hide();
731 }
732
733 static void CreateGtkTextEditor(){
734         auto dlg = ui::Window( ui::window_type::TOP );
735
736         dlg.connect( "delete_event",
737                                           G_CALLBACK( editor_delete ), 0 );
738         gtk_window_set_default_size( dlg, 600, 300 );
739
740         auto vbox = ui::VBox( FALSE, 5 );
741         vbox.show();
742         dlg.add(vbox);
743         gtk_container_set_border_width( GTK_CONTAINER( vbox ), 5 );
744
745         auto scr = ui::ScrolledWindow(ui::New);
746         scr.show();
747         vbox.pack_start( scr, TRUE, TRUE, 0 );
748         gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scr ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
749         gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( scr ), GTK_SHADOW_IN );
750
751         auto text = ui::TextView(ui::New);
752         scr.add(text);
753         text.show();
754         g_object_set_data( G_OBJECT( dlg ), "text", (gpointer) text );
755         gtk_text_view_set_editable( text, TRUE );
756
757         auto hbox = ui::HBox( FALSE, 5 );
758         hbox.show();
759         vbox.pack_start( hbox, FALSE, TRUE, 0 );
760
761         auto button = ui::Button( "Close" );
762         button.show();
763         hbox.pack_end(button, FALSE, FALSE, 0);
764         button.connect( "clicked",
765                                           G_CALLBACK( editor_close ), dlg );
766         button.dimensions(60, -1);
767
768         button = ui::Button( "Save" );
769         button.show();
770         hbox.pack_end(button, FALSE, FALSE, 0);
771         button.connect( "clicked",
772                                           G_CALLBACK( editor_save ), dlg );
773         button.dimensions(60, -1);
774
775         text_editor = dlg;
776         text_widget = text;
777 }
778
779 static void DoGtkTextEditor( const char* filename, guint cursorpos ){
780         if ( !text_editor ) {
781                 CreateGtkTextEditor(); // build it the first time we need it
782
783         }
784         // Load file
785         FILE *f = fopen( filename, "r" );
786
787         if ( f == 0 ) {
788                 globalOutputStream() << "Unable to load file " << filename << " in shader editor.\n";
789                 text_editor.hide();
790         }
791         else
792         {
793                 fseek( f, 0, SEEK_END );
794                 int len = ftell( f );
795                 void *buf = malloc( len );
796                 void *old_filename;
797
798                 rewind( f );
799                 fread( buf, 1, len, f );
800
801                 gtk_window_set_title( text_editor, filename );
802
803                 auto text_buffer = gtk_text_view_get_buffer(ui::TextView::from(text_widget));
804                 gtk_text_buffer_set_text( text_buffer, (char*)buf, len );
805
806                 old_filename = g_object_get_data( G_OBJECT( text_editor ), "filename" );
807                 if ( old_filename ) {
808                         free( old_filename );
809                 }
810                 g_object_set_data( G_OBJECT( text_editor ), "filename", strdup( filename ) );
811
812                 // trying to show later
813                 text_editor.show();
814
815 #if GDEF_OS_WINDOWS
816                 ui::process();
817 #endif
818
819                 // only move the cursor if it's not exceeding the size..
820                 // NOTE: this is erroneous, cursorpos is the offset in bytes, not in characters
821                 // len is the max size in bytes, not in characters either, but the character count is below that limit..
822                 // thinking .. the difference between character count and byte count would be only because of CR/LF?
823                 {
824                         GtkTextIter text_iter;
825                         // character offset, not byte offset
826                         gtk_text_buffer_get_iter_at_offset( text_buffer, &text_iter, cursorpos );
827                         gtk_text_buffer_place_cursor( text_buffer, &text_iter );
828                 }
829
830 #if GDEF_OS_WINDOWS
831                 gtk_widget_queue_draw( text_widget );
832 #endif
833
834                 free( buf );
835                 fclose( f );
836         }
837 }
838
839 // =============================================================================
840 // Light Intensity dialog
841
842 EMessageBoxReturn DoLightIntensityDlg( int *intensity ){
843         ModalDialog dialog;
844         ui::Entry intensity_entry{ui::null};
845         ModalDialogButton ok_button( dialog, eIDOK );
846         ModalDialogButton cancel_button( dialog, eIDCANCEL );
847
848         ui::Window window = MainFrame_getWindow().create_modal_dialog_window("Light intensity", dialog, -1, -1 );
849
850         auto accel_group = ui::AccelGroup(ui::New);
851         window.add_accel_group( accel_group );
852
853         {
854                 auto hbox = create_dialog_hbox( 4, 4 );
855                 window.add(hbox);
856                 {
857             auto vbox = create_dialog_vbox( 4 );
858                         hbox.pack_start( vbox, TRUE, TRUE, 0 );
859                         {
860                                 auto label = ui::Label( "ESC for default, ENTER to validate" );
861                                 label.show();
862                                 vbox.pack_start( label, FALSE, FALSE, 0 );
863                         }
864                         {
865                                 auto entry = ui::Entry(ui::New);
866                                 entry.show();
867                                 vbox.pack_start( entry, TRUE, TRUE, 0 );
868
869                                 gtk_widget_grab_focus( entry  );
870
871                                 intensity_entry = entry;
872                         }
873                 }
874                 {
875             auto vbox = create_dialog_vbox( 4 );
876                         hbox.pack_start( vbox, FALSE, FALSE, 0 );
877
878                         {
879                                 auto button = create_modal_dialog_button( "OK", ok_button );
880                                 vbox.pack_start( button, FALSE, FALSE, 0 );
881                                 widget_make_default( button );
882                                 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
883                         }
884                         {
885                 auto button = create_modal_dialog_button( "Cancel", cancel_button );
886                                 vbox.pack_start( button, FALSE, FALSE, 0 );
887                                 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
888                         }
889                 }
890         }
891
892         char buf[16];
893         sprintf( buf, "%d", *intensity );
894         intensity_entry.text(buf);
895
896         EMessageBoxReturn ret = modal_dialog_show( window, dialog );
897         if ( ret == eIDOK ) {
898                 *intensity = atoi( gtk_entry_get_text( intensity_entry ) );
899         }
900
901         window.destroy();
902
903         return ret;
904 }
905
906 // =============================================================================
907 // Add new shader tag dialog
908
909 EMessageBoxReturn DoShaderTagDlg( CopiedString* tag, const char* title ){
910         ModalDialog dialog;
911         ModalDialogButton ok_button( dialog, eIDOK );
912         ModalDialogButton cancel_button( dialog, eIDCANCEL );
913
914         auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
915
916         auto accel_group = ui::AccelGroup(ui::New);
917         window.add_accel_group( accel_group );
918
919         auto textentry = ui::Entry(ui::New);
920         {
921                 auto hbox = create_dialog_hbox( 4, 4 );
922                 window.add(hbox);
923                 {
924             auto vbox = create_dialog_vbox( 4 );
925                         hbox.pack_start( vbox, TRUE, TRUE, 0 );
926                         {
927                                 //GtkLabel* label = GTK_LABEL(gtk_label_new("Enter one ore more tags separated by spaces"));
928                                 auto label = ui::Label( "ESC to cancel, ENTER to validate" );
929                                 label.show();
930                                 vbox.pack_start( label, FALSE, FALSE, 0 );
931                         }
932                         {
933                                 auto entry = textentry;
934                                 entry.show();
935                                 vbox.pack_start( entry, TRUE, TRUE, 0 );
936
937                                 gtk_widget_grab_focus( entry  );
938                         }
939                 }
940                 {
941             auto vbox = create_dialog_vbox( 4 );
942                         hbox.pack_start( vbox, FALSE, FALSE, 0 );
943
944                         {
945                                 auto button = create_modal_dialog_button( "OK", ok_button );
946                                 vbox.pack_start( button, FALSE, FALSE, 0 );
947                                 widget_make_default( button );
948                                 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
949                         }
950                         {
951                 auto button = create_modal_dialog_button( "Cancel", cancel_button );
952                                 vbox.pack_start( button, FALSE, FALSE, 0 );
953                                 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
954                         }
955                 }
956         }
957
958         EMessageBoxReturn ret = modal_dialog_show( window, dialog );
959         if ( ret == eIDOK ) {
960                 *tag = gtk_entry_get_text( textentry );
961         }
962
963         window.destroy();
964
965         return ret;
966 }
967
968 EMessageBoxReturn DoShaderInfoDlg( const char* name, const char* filename, const char* title ){
969         ModalDialog dialog;
970         ModalDialogButton ok_button( dialog, eIDOK );
971
972         auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
973
974         auto accel_group = ui::AccelGroup(ui::New);
975         window.add_accel_group( accel_group );
976
977         {
978                 auto hbox = create_dialog_hbox( 4, 4 );
979                 window.add(hbox);
980                 {
981             auto vbox = create_dialog_vbox( 4 );
982                         hbox.pack_start( vbox, FALSE, FALSE, 0 );
983                         {
984                                 auto label = ui::Label( "The selected shader" );
985                                 label.show();
986                                 vbox.pack_start( label, FALSE, FALSE, 0 );
987                         }
988                         {
989                                 auto label = ui::Label( name );
990                                 label.show();
991                                 vbox.pack_start( label, FALSE, FALSE, 0 );
992                         }
993                         {
994                                 auto label = ui::Label( "is located in file" );
995                                 label.show();
996                                 vbox.pack_start( label, FALSE, FALSE, 0 );
997                         }
998                         {
999                                 auto label = ui::Label( filename );
1000                                 label.show();
1001                                 vbox.pack_start( label, FALSE, FALSE, 0 );
1002                         }
1003                         {
1004                                 auto button = create_modal_dialog_button( "OK", ok_button );
1005                                 vbox.pack_start( button, FALSE, FALSE, 0 );
1006                                 widget_make_default( button );
1007                                 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
1008                         }
1009                 }
1010         }
1011
1012         EMessageBoxReturn ret = modal_dialog_show( window, dialog );
1013
1014         window.destroy();
1015
1016         return ret;
1017 }
1018
1019
1020
1021 #if GDEF_OS_WINDOWS
1022 #include <gdk/gdkwin32.h>
1023 #endif
1024
1025 #if GDEF_OS_WINDOWS
1026 // use the file associations to open files instead of builtin Gtk editor
1027 bool g_TextEditor_useWin32Editor = true;
1028 #else
1029 // custom shader editor
1030 bool g_TextEditor_useCustomEditor = false;
1031 CopiedString g_TextEditor_editorCommand( "" );
1032 #endif
1033
1034 void DoTextEditor( const char* filename, int cursorpos ){
1035 #if GDEF_OS_WINDOWS
1036         if ( g_TextEditor_useWin32Editor ) {
1037                 globalOutputStream() << "opening file '" << filename << "' (line " << cursorpos << " info ignored)\n";
1038                 ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", filename, 0, 0, SW_SHOW );
1039                 return;
1040         }
1041 #else
1042         // check if a custom editor is set
1043         if ( g_TextEditor_useCustomEditor && !g_TextEditor_editorCommand.empty() ) {
1044                 StringOutputStream strEditCommand( 256 );
1045                 strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << filename << "\"";
1046
1047                 globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n";
1048                 // note: linux does not return false if the command failed so it will assume success
1049                 if ( Q_Exec( 0, const_cast<char*>( strEditCommand.c_str() ), 0, true, false ) == false ) {
1050                         globalOutputStream() << "Failed to execute " << strEditCommand.c_str() << ", using default\n";
1051                 }
1052                 else
1053                 {
1054                         // the command (appeared) to run successfully, no need to do anything more
1055                         return;
1056                 }
1057         }
1058 #endif
1059
1060         DoGtkTextEditor( filename, cursorpos );
1061 }