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() );
425 ModalDialogButton ok_button( dialog, eIDOK );
427 auto window = MainFrame_getWindow().create_modal_dialog_window("About " RADIANT_NAME, dialog );
430 auto vbox = create_dialog_vbox( 4, 4 );
434 auto hbox = create_dialog_hbox( 4 );
435 vbox.pack_start( hbox, FALSE, TRUE, 0 );
438 auto vbox2 = create_dialog_vbox( 4 );
439 hbox.pack_start( vbox2, FALSE, FALSE, 5 );
441 auto frame = create_dialog_frame( 0, ui::Shadow::IN );
442 vbox2.pack_start( frame, FALSE, FALSE, 0 );
444 auto image = new_local_image( "logo.png" );
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"
459 "Get news and latest build at "
460 "<a href='https://netradiant.gitlab.io/'>"
461 "netradiant.gitlab.io"
463 "Please report your issues at "
464 "<a href='https://gitlab.com/xonotic/netradiant/issues'>"
465 "gitlab.com/xonotic/netradiant/issues"
467 "The team cannot provide support"
468 " for custom builds.\n\n"
469 "This program is free software licensed under the GNU GPL.";
471 auto label = ui::Label( label_text );
472 gtk_label_set_use_markup( GTK_LABEL( label ), true );
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 );
481 auto vbox2 = create_dialog_vbox( 4 );
482 hbox.pack_start( vbox2, TRUE, TRUE, 0 );
484 auto button = create_modal_dialog_button( "OK", ok_button );
485 vbox2.pack_start( button, FALSE, FALSE, 0 );
488 auto button = create_dialog_button( "Credits", G_CALLBACK( about_button_credits ), 0 );
489 vbox2.pack_start( button, FALSE, FALSE, 0 );
492 auto button = create_dialog_button( "Changes", G_CALLBACK( about_button_changelog ), 0 );
493 vbox2.pack_start( button, FALSE, FALSE, 0 );
496 auto button = create_dialog_button( "Issues", G_CALLBACK( about_button_issues ), 0 );
497 vbox2.pack_start( button, FALSE, FALSE, 0 );
502 auto frame = create_dialog_frame( "OpenGL Properties" );
503 vbox.pack_start( frame, FALSE, FALSE, 0 );
505 auto table = create_dialog_table( 3, 2, 4, 4, 4 );
508 auto label = ui::Label( "Vendor:" );
510 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
511 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
514 auto label = ui::Label( "Version:" );
516 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
517 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
520 auto label = ui::Label( "Renderer:" );
522 table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0});
523 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
526 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VENDOR ) ) );
528 table.attach(label, {1, 2, 0, 1}, {GTK_FILL, 0});
529 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
532 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VERSION ) ) );
534 table.attach(label, {1, 2, 1, 2}, {GTK_FILL, 0});
535 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
538 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_RENDERER ) ) );
540 table.attach(label, {1, 2, 2, 3}, {GTK_FILL, 0});
541 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
545 auto frame = create_dialog_frame( "OpenGL Extensions" );
546 vbox.pack_start( frame, TRUE, TRUE, 0 );
548 auto sc_extensions = create_scrolled_window( ui::Policy::AUTOMATIC, ui::Policy::ALWAYS, 4 );
549 frame.add(sc_extensions);
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();
563 modal_dialog_show( window, dialog );
568 // =============================================================================
569 // TextureLayout dialog
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;
575 EMessageBoxReturn DoTextureLayout( float *fx, float *fy ){
577 ModalDialogButton ok_button( dialog, eIDOK );
578 ModalDialogButton cancel_button( dialog, eIDCANCEL );
579 ui::Entry x{ui::null};
580 ui::Entry y{ui::null};
582 auto window = MainFrame_getWindow().create_modal_dialog_window("Patch texture layout", dialog );
584 auto accel = ui::AccelGroup(ui::New);
585 window.add_accel_group( accel );
588 auto hbox = create_dialog_hbox( 4, 4 );
591 auto vbox = create_dialog_vbox( 4 );
592 hbox.pack_start( vbox, TRUE, TRUE, 0 );
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"
599 vbox.pack_start( label, TRUE, TRUE, 0 );
600 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
603 auto table = create_dialog_table( 2, 2, 4, 4 );
605 vbox.pack_start( table, TRUE, TRUE, 0 );
607 auto label = ui::Label( "Texture x:" );
609 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
610 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
613 auto label = ui::Label( "Texture y:" );
615 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
616 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
619 auto entry = ui::Entry(ui::New);
621 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
626 auto entry = ui::Entry(ui::New);
628 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
635 auto vbox = create_dialog_vbox( 4 );
636 hbox.pack_start( vbox, FALSE, FALSE, 0 );
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 );
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 );
651 // Initialize with last used values
654 sprintf( buf, "%f", last_used_texture_layout_scale_x );
657 sprintf( buf, "%f", last_used_texture_layout_scale_y );
660 // Set focus after intializing the values
661 gtk_widget_grab_focus( x );
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 ) ) );
668 // Remember last used values
669 last_used_texture_layout_scale_x = *fx;
670 last_used_texture_layout_scale_y = *fy;
678 // =============================================================================
679 // Text Editor dialog
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
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 ) {
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" );
700 ui::alert( ui::Widget::from(data).window(), "Error saving file !" );
704 char *str = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
705 fwrite( str, 1, strlen( str ), f );
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 ) {
717 static void CreateGtkTextEditor(){
718 auto dlg = ui::Window( ui::window_type::TOP );
720 dlg.connect( "delete_event",
721 G_CALLBACK( editor_delete ), 0 );
722 gtk_window_set_default_size( dlg, 600, 300 );
724 auto vbox = ui::VBox( FALSE, 5 );
727 gtk_container_set_border_width( GTK_CONTAINER( vbox ), 5 );
729 auto scr = ui::ScrolledWindow(ui::New);
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 );
735 auto text = ui::TextView(ui::New);
738 g_object_set_data( G_OBJECT( dlg ), "text", (gpointer) text );
739 gtk_text_view_set_editable( text, TRUE );
741 auto hbox = ui::HBox( FALSE, 5 );
743 vbox.pack_start( hbox, FALSE, TRUE, 0 );
745 auto button = ui::Button( "Close" );
747 hbox.pack_end(button, FALSE, FALSE, 0);
748 button.connect( "clicked",
749 G_CALLBACK( editor_close ), dlg );
750 button.dimensions(60, -1);
752 button = ui::Button( "Save" );
754 hbox.pack_end(button, FALSE, FALSE, 0);
755 button.connect( "clicked",
756 G_CALLBACK( editor_save ), dlg );
757 button.dimensions(60, -1);
763 static void DoGtkTextEditor( const char* filename, guint cursorpos ){
764 if ( !text_editor ) {
765 CreateGtkTextEditor(); // build it the first time we need it
769 FILE *f = fopen( filename, "r" );
772 globalOutputStream() << "Unable to load file " << filename << " in shader editor.\n";
777 fseek( f, 0, SEEK_END );
778 int len = ftell( f );
779 void *buf = malloc( len );
783 fread( buf, 1, len, f );
785 gtk_window_set_title( text_editor, filename );
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 );
790 old_filename = g_object_get_data( G_OBJECT( text_editor ), "filename" );
791 if ( old_filename ) {
792 free( old_filename );
794 g_object_set_data( G_OBJECT( text_editor ), "filename", strdup( filename ) );
796 // trying to show later
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?
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 );
815 gtk_widget_queue_draw( text_widget );
823 // =============================================================================
824 // Light Intensity dialog
826 EMessageBoxReturn DoLightIntensityDlg( int *intensity ){
828 ui::Entry intensity_entry{ui::null};
829 ModalDialogButton ok_button( dialog, eIDOK );
830 ModalDialogButton cancel_button( dialog, eIDCANCEL );
832 ui::Window window = MainFrame_getWindow().create_modal_dialog_window("Light intensity", dialog, -1, -1 );
834 auto accel_group = ui::AccelGroup(ui::New);
835 window.add_accel_group( accel_group );
838 auto hbox = create_dialog_hbox( 4, 4 );
841 auto vbox = create_dialog_vbox( 4 );
842 hbox.pack_start( vbox, TRUE, TRUE, 0 );
844 auto label = ui::Label( "ESC for default, ENTER to validate" );
846 vbox.pack_start( label, FALSE, FALSE, 0 );
849 auto entry = ui::Entry(ui::New);
851 vbox.pack_start( entry, TRUE, TRUE, 0 );
853 gtk_widget_grab_focus( entry );
855 intensity_entry = entry;
859 auto vbox = create_dialog_vbox( 4 );
860 hbox.pack_start( vbox, FALSE, FALSE, 0 );
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 );
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 );
877 sprintf( buf, "%d", *intensity );
878 intensity_entry.text(buf);
880 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
881 if ( ret == eIDOK ) {
882 *intensity = atoi( gtk_entry_get_text( intensity_entry ) );
890 // =============================================================================
891 // Add new shader tag dialog
893 EMessageBoxReturn DoShaderTagDlg( CopiedString* tag, const char* title ){
895 ModalDialogButton ok_button( dialog, eIDOK );
896 ModalDialogButton cancel_button( dialog, eIDCANCEL );
898 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
900 auto accel_group = ui::AccelGroup(ui::New);
901 window.add_accel_group( accel_group );
903 auto textentry = ui::Entry(ui::New);
905 auto hbox = create_dialog_hbox( 4, 4 );
908 auto vbox = create_dialog_vbox( 4 );
909 hbox.pack_start( vbox, TRUE, TRUE, 0 );
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" );
914 vbox.pack_start( label, FALSE, FALSE, 0 );
917 auto entry = textentry;
919 vbox.pack_start( entry, TRUE, TRUE, 0 );
921 gtk_widget_grab_focus( entry );
925 auto vbox = create_dialog_vbox( 4 );
926 hbox.pack_start( vbox, FALSE, FALSE, 0 );
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 );
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 );
942 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
943 if ( ret == eIDOK ) {
944 *tag = gtk_entry_get_text( textentry );
952 EMessageBoxReturn DoShaderInfoDlg( const char* name, const char* filename, const char* title ){
954 ModalDialogButton ok_button( dialog, eIDOK );
956 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
958 auto accel_group = ui::AccelGroup(ui::New);
959 window.add_accel_group( accel_group );
962 auto hbox = create_dialog_hbox( 4, 4 );
965 auto vbox = create_dialog_vbox( 4 );
966 hbox.pack_start( vbox, FALSE, FALSE, 0 );
968 auto label = ui::Label( "The selected shader" );
970 vbox.pack_start( label, FALSE, FALSE, 0 );
973 auto label = ui::Label( name );
975 vbox.pack_start( label, FALSE, FALSE, 0 );
978 auto label = ui::Label( "is located in file" );
980 vbox.pack_start( label, FALSE, FALSE, 0 );
983 auto label = ui::Label( filename );
985 vbox.pack_start( label, FALSE, FALSE, 0 );
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 );
996 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
1006 #include <gdk/gdkwin32.h>
1010 // use the file associations to open files instead of builtin Gtk editor
1011 bool g_TextEditor_useWin32Editor = true;
1013 // custom shader editor
1014 bool g_TextEditor_useCustomEditor = false;
1015 CopiedString g_TextEditor_editorCommand( "" );
1018 void DoTextEditor( const char* filename, int cursorpos ){
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 );
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 << "\"";
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";
1038 // the command (appeared) to run successfully, no need to do anything more
1044 DoGtkTextEditor( filename, cursorpos );