2 Copyright (c) 2001, Loki software, inc.
5 Redistribution and use in source and binary forms, with or without modification,
6 are permitted provided that the following conditions are met:
8 Redistributions of source code must retain the above copyright notice, this list
9 of conditions and the following disclaimer.
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.
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
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.
32 // Some small dialogs that don't need much
34 // Leonardo Zide (leo@lokigames.com)
38 #include "globaldefs.h"
42 #include "debugging/debugging.h"
46 #include "iscenegraph.h"
47 #include "iselection.h"
49 #include <gdk/gdkkeysyms.h>
50 #include <uilib/uilib.h>
53 #include "math/aabb.h"
54 #include "container/array.h"
55 #include "generic/static.h"
56 #include "stream/stringstream.h"
58 #include "gtkutil/messagebox.h"
59 #include "gtkutil/image.h"
62 #include "brushmanip.h"
65 #include "texwindow.h"
67 #include "mainframe.h"
68 #include "preferences.h"
74 // =============================================================================
75 // Project settings dialog
77 class GameComboConfiguration
80 const char* basegame_dir;
82 const char* known_dir;
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" ) ){
95 typedef LazyStatic<GameComboConfiguration> LazyStaticGameComboConfiguration;
97 inline GameComboConfiguration& globalGameComboConfiguration(){
98 return LazyStaticGameComboConfiguration::instance();
104 gamecombo_t( int _game, const char* _fs_game, bool _sensitive )
105 : game( _game ), fs_game( _fs_game ), sensitive( _sensitive )
112 gamecombo_t gamecombo_for_dir( const char* dir ){
113 if ( string_equal( dir, globalGameComboConfiguration().basegame_dir ) ) {
114 return gamecombo_t( 0, "", false );
116 else if ( string_equal( dir, globalGameComboConfiguration().known_dir ) ) {
117 return gamecombo_t( 1, dir, false );
121 return gamecombo_t( string_empty( globalGameComboConfiguration().known_dir ) ? 1 : 2, dir, true );
125 gamecombo_t gamecombo_for_gamename( const char* gamename ){
126 if ( ( strlen( gamename ) == 0 ) || !strcmp( gamename, globalGameComboConfiguration().basegame ) ) {
127 return gamecombo_t( 0, "", false );
129 else if ( !strcmp( gamename, globalGameComboConfiguration().known ) ) {
130 return gamecombo_t( 1, globalGameComboConfiguration().known_dir, false );
134 return gamecombo_t( string_empty( globalGameComboConfiguration().known_dir ) ? 1 : 2, "", true );
138 inline void path_copy_clean( char* destination, const char* source ){
139 char* i = destination;
141 while ( *source != '\0' )
143 *i++ = ( *source == '\\' ) ? '/' : *source;
147 if ( i != destination && *( i - 1 ) != '/' ) {
157 ui::ComboBoxText game_select{ui::null};
158 ui::Entry fsgame_entry{ui::null};
161 gboolean OnSelchangeComboWhatgame( ui::Widget widget, GameCombo* combo ){
162 const char *gamename;
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 );
169 gamecombo_t gamecombo = gamecombo_for_gamename( gamename );
171 combo->fsgame_entry.text( gamecombo.fs_game );
172 gtk_widget_set_sensitive( combo->fsgame_entry , gamecombo.sensitive );
180 bool do_mapping_mode;
181 const char* sp_mapping_mode;
182 const char* mp_mapping_mode;
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" ){
191 typedef LazyStatic<MappingMode> LazyStaticMappingMode;
193 inline MappingMode& globalMappingMode(){
194 return LazyStaticMappingMode::instance();
197 class ProjectSettingsDialog
200 GameCombo game_combo;
201 ui::ComboBox gamemode_combo{ui::null};
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 );
208 auto table1 = create_dialog_table( 1, 2, 4, 4, 4 );
211 auto vbox = create_dialog_vbox( 4 );
212 table1.attach(vbox, {1, 2, 0, 1}, {GTK_FILL, GTK_FILL});
214 auto button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &modal );
215 vbox.pack_start( button, FALSE, FALSE, 0 );
218 auto button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &modal );
219 vbox.pack_start( button, FALSE, FALSE, 0 );
223 auto frame = create_dialog_frame( "Project settings" );
224 table1.attach(frame, {0, 1, 0, 1}, {GTK_EXPAND | GTK_FILL, GTK_FILL});
226 auto table2 = create_dialog_table( ( globalMappingMode().do_mapping_mode ) ? 4 : 3, 2, 4, 4, 4 );
230 auto label = ui::Label( "Select mod" );
232 table2.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
233 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
236 dialog.game_combo.game_select = ui::ComboBoxText(ui::New);
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 );
242 gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().custom );
244 dialog.game_combo.game_select.show();
245 table2.attach(dialog.game_combo.game_select, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
247 dialog.game_combo.game_select.connect( "changed", G_CALLBACK( OnSelchangeComboWhatgame ), &dialog.game_combo );
251 auto label = ui::Label( "fs_game" );
253 table2.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
254 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
257 auto entry = ui::Entry(ui::New);
259 table2.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
261 dialog.game_combo.fsgame_entry = entry;
264 if ( globalMappingMode().do_mapping_mode ) {
265 auto label = ui::Label( "Mapping mode" );
267 table2.attach(label, {0, 1, 3, 4}, {GTK_FILL, 0});
268 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
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 );
275 table2.attach(combo, {1, 2, 3, 4}, {GTK_EXPAND | GTK_FILL, 0});
277 dialog.gamemode_combo = combo;
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 );
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 );
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 );
298 gtk_combo_box_set_active( dialog.gamemode_combo, 1 );
305 void ProjectSettingsDialog_ok( ProjectSettingsDialog& dialog ){
306 const char* dir = gtk_entry_get_text( dialog.game_combo.fsgame_entry );
308 const char* new_gamename = path_equal( dir, globalGameComboConfiguration().basegame_dir )
312 if ( !path_equal( new_gamename, gamename_get() ) ) {
313 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Changing Game Name" );
315 EnginePath_Unrealise();
317 gamename_set( new_gamename );
319 EnginePath_Realise();
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" );
330 gamemode_set( "mp" );
335 void DoProjectSettings(){
336 if ( ConfirmModified( "Edit Project Settings" ) ) {
338 ProjectSettingsDialog dialog;
340 ui::Window window = ProjectSettingsDialog_construct( dialog, modal );
342 if ( modal_dialog_show( window, modal ) == eIDOK ) {
343 ProjectSettingsDialog_ok( dialog );
350 // =============================================================================
351 // Arbitrary Sides dialog
353 void DoSides( int type, int axis ){
356 auto window = MainFrame_getWindow().create_dialog_window("Arbitrary sides", G_CALLBACK(dialog_delete_callback ), &dialog );
358 auto accel = ui::AccelGroup(ui::New);
359 window.add_accel_group( accel );
361 auto sides_entry = ui::Entry(ui::New);
363 auto hbox = create_dialog_hbox( 4, 4 );
366 auto label = ui::Label( "Sides:" );
368 hbox.pack_start( label, FALSE, FALSE, 0 );
371 auto entry = sides_entry;
373 hbox.pack_start( entry, FALSE, FALSE, 0 );
374 gtk_widget_grab_focus( entry );
377 auto vbox = create_dialog_vbox( 4 );
378 hbox.pack_start( vbox, TRUE, TRUE, 0 );
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 );
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 );
393 if ( modal_dialog_show( window, dialog ) == eIDOK ) {
394 const char *str = gtk_entry_get_text( sides_entry );
396 Scene_BrushConstructPrefab( GlobalSceneGraph(), (EBrushPrefab)type, atoi( str ), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
402 // =============================================================================
403 // About dialog (no program is complete without one)
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() );
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() );
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() );
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 );
429 vbox.pack_start( label, TRUE, TRUE, 0 );
432 static void AddParagraph( ui::VBox vbox, const char* text ){
433 AddParagraph( vbox, text, false );
438 ModalDialogButton ok_button( dialog, eIDOK );
440 auto window = MainFrame_getWindow().create_modal_dialog_window("About " RADIANT_NAME, dialog );
443 auto vbox = create_dialog_vbox( 4, 4 );
447 auto hbox = create_dialog_hbox( 4 );
448 vbox.pack_start( hbox, FALSE, FALSE, 0 );
451 auto vbox2 = create_dialog_vbox( 4 );
452 hbox.pack_start( vbox2, FALSE, FALSE, 5 );
454 auto frame = create_dialog_frame( 0, ui::Shadow::IN );
455 vbox2.pack_start( frame, FALSE, FALSE, 0 );
457 auto image = new_local_image( "logo.png" );
465 // HACK: that may not be related to font size
466 auto about_vbox = ui::VBox( FALSE, 5 );
468 hbox.pack_start( about_vbox, FALSE, FALSE, 0 );
470 AddParagraph( about_vbox,
471 RADIANT_NAME " " RADIANT_VERSION_STRING " (" __DATE__ ")\n"
473 AddParagraph( about_vbox,
474 "Get news and latest build at "
475 "<a href='https://netradiant.gitlab.io/'>"
476 "netradiant.gitlab.io"
478 "Please report your issues at "
479 "<a href='https://gitlab.com/xonotic/netradiant/issues'>"
480 "gitlab.com/xonotic/netradiant/issues"
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'>"
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." );
497 auto vbox2 = create_dialog_vbox( 4 );
498 hbox.pack_start( vbox2, TRUE, TRUE, 0 );
500 auto button = create_modal_dialog_button( "OK", ok_button );
501 vbox2.pack_start( button, FALSE, FALSE, 0 );
502 gtk_widget_grab_focus( GTK_WIDGET( button ) );
505 auto button = create_dialog_button( "Credits", G_CALLBACK( about_button_credits ), 0 );
506 vbox2.pack_start( button, FALSE, FALSE, 0 );
509 auto button = create_dialog_button( "Changes", G_CALLBACK( about_button_changelog ), 0 );
510 vbox2.pack_start( button, FALSE, FALSE, 0 );
513 auto button = create_dialog_button( "Issues", G_CALLBACK( about_button_issues ), 0 );
514 vbox2.pack_start( button, FALSE, FALSE, 0 );
519 auto frame = create_dialog_frame( "OpenGL Properties" );
520 vbox.pack_start( frame, FALSE, FALSE, 0 );
522 auto table = create_dialog_table( 3, 2, 4, 4, 4 );
525 auto label = ui::Label( "Vendor:" );
527 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
528 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
531 auto label = ui::Label( "Version:" );
533 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
534 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
537 auto label = ui::Label( "Renderer:" );
539 table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0});
540 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
543 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VENDOR ) ) );
545 table.attach(label, {1, 2, 0, 1}, {GTK_FILL, 0});
546 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
549 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VERSION ) ) );
551 table.attach(label, {1, 2, 1, 2}, {GTK_FILL, 0});
552 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
555 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_RENDERER ) ) );
557 table.attach(label, {1, 2, 2, 3}, {GTK_FILL, 0});
558 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
562 auto frame = create_dialog_frame( "OpenGL Extensions" );
563 vbox.pack_start( frame, TRUE, TRUE, 0 );
565 auto sc_extensions = create_scrolled_window( ui::Policy::AUTOMATIC, ui::Policy::ALWAYS, 4 );
566 frame.add(sc_extensions);
568 auto text_extensions = ui::TextView(ui::New);
569 gtk_text_view_set_editable( text_extensions, FALSE );
570 sc_extensions.add(text_extensions);
571 text_extensions.text(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)));
572 gtk_text_view_set_wrap_mode( text_extensions, GTK_WRAP_WORD );
573 text_extensions.show();
580 modal_dialog_show( window, dialog );
585 // =============================================================================
586 // TextureLayout dialog
588 // Last used texture scale values
589 static float last_used_texture_layout_scale_x = 4.0;
590 static float last_used_texture_layout_scale_y = 4.0;
592 EMessageBoxReturn DoTextureLayout( float *fx, float *fy ){
594 ModalDialogButton ok_button( dialog, eIDOK );
595 ModalDialogButton cancel_button( dialog, eIDCANCEL );
596 ui::Entry x{ui::null};
597 ui::Entry y{ui::null};
599 auto window = MainFrame_getWindow().create_modal_dialog_window("Patch texture layout", dialog );
601 auto accel = ui::AccelGroup(ui::New);
602 window.add_accel_group( accel );
605 auto hbox = create_dialog_hbox( 4, 4 );
608 auto vbox = create_dialog_vbox( 4 );
609 hbox.pack_start( vbox, TRUE, TRUE, 0 );
611 auto label = ui::Label( "Texture will be fit across the patch based\n"
612 "on the x and y values given. Values of 1x1\n"
613 "will \"fit\" the texture. 2x2 will repeat\n"
616 vbox.pack_start( label, TRUE, TRUE, 0 );
617 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
620 auto table = create_dialog_table( 2, 2, 4, 4 );
622 vbox.pack_start( table, TRUE, TRUE, 0 );
624 auto label = ui::Label( "Texture x:" );
626 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
627 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
630 auto label = ui::Label( "Texture y:" );
632 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
633 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
636 auto entry = ui::Entry(ui::New);
638 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
643 auto entry = ui::Entry(ui::New);
645 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
652 auto vbox = create_dialog_vbox( 4 );
653 hbox.pack_start( vbox, FALSE, FALSE, 0 );
655 auto button = create_modal_dialog_button( "OK", ok_button );
656 vbox.pack_start( button, FALSE, FALSE, 0 );
657 widget_make_default( button );
658 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
661 auto button = create_modal_dialog_button( "Cancel", cancel_button );
662 vbox.pack_start( button, FALSE, FALSE, 0 );
663 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
668 // Initialize with last used values
671 sprintf( buf, "%f", last_used_texture_layout_scale_x );
674 sprintf( buf, "%f", last_used_texture_layout_scale_y );
677 // Set focus after intializing the values
678 gtk_widget_grab_focus( x );
680 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
681 if ( ret == eIDOK ) {
682 *fx = static_cast<float>( atof( gtk_entry_get_text( x ) ) );
683 *fy = static_cast<float>( atof( gtk_entry_get_text( y ) ) );
685 // Remember last used values
686 last_used_texture_layout_scale_x = *fx;
687 last_used_texture_layout_scale_y = *fy;
695 // =============================================================================
696 // Text Editor dialog
698 // master window widget
699 static ui::Window text_editor{ui::null};
700 static ui::Widget text_widget{ui::null}; // slave, text widget from the gtk editor
702 static gint editor_delete( ui::Widget widget, gpointer data ){
703 if ( ui::alert( widget.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
712 static void editor_save( ui::Widget widget, gpointer data ){
713 FILE *f = fopen( (char*)g_object_get_data( G_OBJECT( data ), "filename" ), "w" );
714 gpointer text = g_object_get_data( G_OBJECT( data ), "text" );
717 ui::alert( ui::Widget::from(data).window(), "Error saving file !" );
721 char *str = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
722 fwrite( str, 1, strlen( str ), f );
726 static void editor_close( ui::Widget widget, gpointer data ){
727 if ( ui::alert( text_editor.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
734 static void CreateGtkTextEditor(){
735 auto dlg = ui::Window( ui::window_type::TOP );
737 dlg.connect( "delete_event",
738 G_CALLBACK( editor_delete ), 0 );
739 gtk_window_set_default_size( dlg, 600, 300 );
741 auto vbox = ui::VBox( FALSE, 5 );
744 gtk_container_set_border_width( GTK_CONTAINER( vbox ), 5 );
746 auto scr = ui::ScrolledWindow(ui::New);
748 vbox.pack_start( scr, TRUE, TRUE, 0 );
749 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scr ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
750 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( scr ), GTK_SHADOW_IN );
752 auto text = ui::TextView(ui::New);
755 g_object_set_data( G_OBJECT( dlg ), "text", (gpointer) text );
756 gtk_text_view_set_editable( text, TRUE );
758 auto hbox = ui::HBox( FALSE, 5 );
760 vbox.pack_start( hbox, FALSE, TRUE, 0 );
762 auto button = ui::Button( "Close" );
764 hbox.pack_end(button, FALSE, FALSE, 0);
765 button.connect( "clicked",
766 G_CALLBACK( editor_close ), dlg );
767 button.dimensions(60, -1);
769 button = ui::Button( "Save" );
771 hbox.pack_end(button, FALSE, FALSE, 0);
772 button.connect( "clicked",
773 G_CALLBACK( editor_save ), dlg );
774 button.dimensions(60, -1);
780 static void DoGtkTextEditor( const char* filename, guint cursorpos ){
781 if ( !text_editor ) {
782 CreateGtkTextEditor(); // build it the first time we need it
786 FILE *f = fopen( filename, "r" );
789 globalOutputStream() << "Unable to load file " << filename << " in shader editor.\n";
794 fseek( f, 0, SEEK_END );
795 int len = ftell( f );
796 void *buf = malloc( len );
800 fread( buf, 1, len, f );
802 gtk_window_set_title( text_editor, filename );
804 auto text_buffer = gtk_text_view_get_buffer(ui::TextView::from(text_widget));
805 gtk_text_buffer_set_text( text_buffer, (char*)buf, len );
807 old_filename = g_object_get_data( G_OBJECT( text_editor ), "filename" );
808 if ( old_filename ) {
809 free( old_filename );
811 g_object_set_data( G_OBJECT( text_editor ), "filename", strdup( filename ) );
813 // trying to show later
820 // only move the cursor if it's not exceeding the size..
821 // NOTE: this is erroneous, cursorpos is the offset in bytes, not in characters
822 // len is the max size in bytes, not in characters either, but the character count is below that limit..
823 // thinking .. the difference between character count and byte count would be only because of CR/LF?
825 GtkTextIter text_iter;
826 // character offset, not byte offset
827 gtk_text_buffer_get_iter_at_offset( text_buffer, &text_iter, cursorpos );
828 gtk_text_buffer_place_cursor( text_buffer, &text_iter );
832 gtk_widget_queue_draw( text_widget );
840 // =============================================================================
841 // Light Intensity dialog
843 EMessageBoxReturn DoLightIntensityDlg( int *intensity ){
845 ui::Entry intensity_entry{ui::null};
846 ModalDialogButton ok_button( dialog, eIDOK );
847 ModalDialogButton cancel_button( dialog, eIDCANCEL );
849 ui::Window window = MainFrame_getWindow().create_modal_dialog_window("Light intensity", dialog, -1, -1 );
851 auto accel_group = ui::AccelGroup(ui::New);
852 window.add_accel_group( accel_group );
855 auto hbox = create_dialog_hbox( 4, 4 );
858 auto vbox = create_dialog_vbox( 4 );
859 hbox.pack_start( vbox, TRUE, TRUE, 0 );
861 auto label = ui::Label( "ESC for default, ENTER to validate" );
863 vbox.pack_start( label, FALSE, FALSE, 0 );
866 auto entry = ui::Entry(ui::New);
868 vbox.pack_start( entry, TRUE, TRUE, 0 );
870 gtk_widget_grab_focus( entry );
872 intensity_entry = entry;
876 auto vbox = create_dialog_vbox( 4 );
877 hbox.pack_start( vbox, FALSE, FALSE, 0 );
880 auto button = create_modal_dialog_button( "OK", ok_button );
881 vbox.pack_start( button, FALSE, FALSE, 0 );
882 widget_make_default( button );
883 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
886 auto button = create_modal_dialog_button( "Cancel", cancel_button );
887 vbox.pack_start( button, FALSE, FALSE, 0 );
888 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
894 sprintf( buf, "%d", *intensity );
895 intensity_entry.text(buf);
897 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
898 if ( ret == eIDOK ) {
899 *intensity = atoi( gtk_entry_get_text( intensity_entry ) );
907 // =============================================================================
908 // Add new shader tag dialog
910 EMessageBoxReturn DoShaderTagDlg( CopiedString* tag, const char* title ){
912 ModalDialogButton ok_button( dialog, eIDOK );
913 ModalDialogButton cancel_button( dialog, eIDCANCEL );
915 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
917 auto accel_group = ui::AccelGroup(ui::New);
918 window.add_accel_group( accel_group );
920 auto textentry = ui::Entry(ui::New);
922 auto hbox = create_dialog_hbox( 4, 4 );
925 auto vbox = create_dialog_vbox( 4 );
926 hbox.pack_start( vbox, TRUE, TRUE, 0 );
928 //GtkLabel* label = GTK_LABEL(gtk_label_new("Enter one ore more tags separated by spaces"));
929 auto label = ui::Label( "ESC to cancel, ENTER to validate" );
931 vbox.pack_start( label, FALSE, FALSE, 0 );
934 auto entry = textentry;
936 vbox.pack_start( entry, TRUE, TRUE, 0 );
938 gtk_widget_grab_focus( entry );
942 auto vbox = create_dialog_vbox( 4 );
943 hbox.pack_start( vbox, FALSE, FALSE, 0 );
946 auto button = create_modal_dialog_button( "OK", ok_button );
947 vbox.pack_start( button, FALSE, FALSE, 0 );
948 widget_make_default( button );
949 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
952 auto button = create_modal_dialog_button( "Cancel", cancel_button );
953 vbox.pack_start( button, FALSE, FALSE, 0 );
954 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
959 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
960 if ( ret == eIDOK ) {
961 *tag = gtk_entry_get_text( textentry );
969 EMessageBoxReturn DoShaderInfoDlg( const char* name, const char* filename, const char* title ){
971 ModalDialogButton ok_button( dialog, eIDOK );
973 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
975 auto accel_group = ui::AccelGroup(ui::New);
976 window.add_accel_group( accel_group );
979 auto hbox = create_dialog_hbox( 4, 4 );
982 auto vbox = create_dialog_vbox( 4 );
983 hbox.pack_start( vbox, FALSE, FALSE, 0 );
985 auto label = ui::Label( "The selected shader" );
987 vbox.pack_start( label, FALSE, FALSE, 0 );
990 auto label = ui::Label( name );
992 vbox.pack_start( label, FALSE, FALSE, 0 );
995 auto label = ui::Label( "is located in file" );
997 vbox.pack_start( label, FALSE, FALSE, 0 );
1000 auto label = ui::Label( filename );
1002 vbox.pack_start( label, FALSE, FALSE, 0 );
1005 auto button = create_modal_dialog_button( "OK", ok_button );
1006 vbox.pack_start( button, FALSE, FALSE, 0 );
1007 widget_make_default( button );
1008 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
1013 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
1023 #include <gdk/gdkwin32.h>
1027 // use the file associations to open files instead of builtin Gtk editor
1028 bool g_TextEditor_useWin32Editor = true;
1030 // custom shader editor
1031 bool g_TextEditor_useCustomEditor = false;
1032 CopiedString g_TextEditor_editorCommand( "" );
1035 void DoTextEditor( const char* filename, int cursorpos ){
1037 if ( g_TextEditor_useWin32Editor ) {
1038 globalOutputStream() << "opening file '" << filename << "' (line " << cursorpos << " info ignored)\n";
1039 ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", filename, 0, 0, SW_SHOW );
1043 // check if a custom editor is set
1044 if ( g_TextEditor_useCustomEditor && !g_TextEditor_editorCommand.empty() ) {
1045 StringOutputStream strEditCommand( 256 );
1046 strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << filename << "\"";
1048 globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n";
1049 // note: linux does not return false if the command failed so it will assume success
1050 if ( Q_Exec( 0, const_cast<char*>( strEditCommand.c_str() ), 0, true, false ) == false ) {
1051 globalOutputStream() << "Failed to execute " << strEditCommand.c_str() << ", using default\n";
1055 // the command (appeared) to run successfully, no need to do anything more
1061 DoGtkTextEditor( filename, cursorpos );