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