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"
47 #include "iscenegraph.h"
48 #include "iselection.h"
50 #include <gdk/gdkkeysyms.h>
51 #include <uilib/uilib.h>
54 #include "math/aabb.h"
55 #include "container/array.h"
56 #include "generic/static.h"
57 #include "stream/stringstream.h"
59 #include "gtkutil/messagebox.h"
60 #include "gtkutil/image.h"
63 #include "brushmanip.h"
66 #include "texwindow.h"
68 #include "mainframe.h"
69 #include "preferences.h"
75 // =============================================================================
76 // Project settings dialog
78 class GameComboConfiguration
81 const char* basegame_dir;
83 const char* known_dir;
87 GameComboConfiguration() :
88 basegame_dir( g_pGameDescription->getRequiredKeyValue( "basegame" ) ),
89 basegame( g_pGameDescription->getRequiredKeyValue( "basegamename" ) ),
90 known_dir( g_pGameDescription->getKeyValue( "knowngame" ) ),
91 known( g_pGameDescription->getKeyValue( "knowngamename" ) ),
92 custom( g_pGameDescription->getRequiredKeyValue( "unknowngamename" ) ){
96 typedef LazyStatic<GameComboConfiguration> LazyStaticGameComboConfiguration;
98 inline GameComboConfiguration& globalGameComboConfiguration(){
99 return LazyStaticGameComboConfiguration::instance();
105 gamecombo_t( int _game, const char* _fs_game, bool _sensitive )
106 : game( _game ), fs_game( _fs_game ), sensitive( _sensitive )
113 gamecombo_t gamecombo_for_dir( const char* dir ){
114 if ( string_equal( dir, globalGameComboConfiguration().basegame_dir ) ) {
115 return gamecombo_t( 0, "", false );
117 else if ( string_equal( dir, globalGameComboConfiguration().known_dir ) ) {
118 return gamecombo_t( 1, dir, false );
122 return gamecombo_t( string_empty( globalGameComboConfiguration().known_dir ) ? 1 : 2, dir, true );
126 gamecombo_t gamecombo_for_gamename( const char* gamename ){
127 if ( ( strlen( gamename ) == 0 ) || !strcmp( gamename, globalGameComboConfiguration().basegame ) ) {
128 return gamecombo_t( 0, "", false );
130 else if ( !strcmp( gamename, globalGameComboConfiguration().known ) ) {
131 return gamecombo_t( 1, globalGameComboConfiguration().known_dir, false );
135 return gamecombo_t( string_empty( globalGameComboConfiguration().known_dir ) ? 1 : 2, "", true );
139 inline void path_copy_clean( char* destination, const char* source ){
140 char* i = destination;
142 while ( *source != '\0' )
144 *i++ = ( *source == '\\' ) ? '/' : *source;
148 if ( i != destination && *( i - 1 ) != '/' ) {
158 ui::ComboBoxText game_select{ui::null};
159 ui::Entry fsgame_entry{ui::null};
162 gboolean OnSelchangeComboWhatgame( ui::Widget widget, GameCombo* combo ){
163 const char *gamename;
166 gtk_combo_box_get_active_iter( combo->game_select, &iter );
167 gtk_tree_model_get( gtk_combo_box_get_model( combo->game_select ), &iter, 0, (gpointer*)&gamename, -1 );
170 gamecombo_t gamecombo = gamecombo_for_gamename( gamename );
172 combo->fsgame_entry.text( gamecombo.fs_game );
173 gtk_widget_set_sensitive( combo->fsgame_entry , gamecombo.sensitive );
181 bool do_mapping_mode;
182 const char* sp_mapping_mode;
183 const char* mp_mapping_mode;
186 do_mapping_mode( !string_empty( g_pGameDescription->getKeyValue( "show_gamemode" ) ) ),
187 sp_mapping_mode( "Single Player mapping mode" ),
188 mp_mapping_mode( "Multiplayer mapping mode" ){
192 typedef LazyStatic<MappingMode> LazyStaticMappingMode;
194 inline MappingMode& globalMappingMode(){
195 return LazyStaticMappingMode::instance();
198 class ProjectSettingsDialog
201 GameCombo game_combo;
202 GtkComboBox* gamemode_combo;
205 ui::Window ProjectSettingsDialog_construct( ProjectSettingsDialog& dialog, ModalDialog& modal ){
206 auto window = MainFrame_getWindow().create_dialog_window("Project Settings", G_CALLBACK(dialog_delete_callback ), &modal );
209 auto table1 = create_dialog_table( 1, 2, 4, 4, 4 );
212 auto vbox = create_dialog_vbox( 4 );
213 table1.attach(vbox, {1, 2, 0, 1}, {GTK_FILL, GTK_FILL});
215 auto button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &modal );
216 vbox.pack_start( button, FALSE, FALSE, 0 );
219 auto button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &modal );
220 vbox.pack_start( button, FALSE, FALSE, 0 );
224 auto frame = create_dialog_frame( "Project settings" );
225 table1.attach(frame, {0, 1, 0, 1}, {GTK_EXPAND | GTK_FILL, GTK_FILL});
227 auto table2 = create_dialog_table( ( globalMappingMode().do_mapping_mode ) ? 4 : 3, 2, 4, 4, 4 );
231 auto label = ui::Label( "Select mod" );
233 table2.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
234 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
237 dialog.game_combo.game_select = ui::ComboBoxText(ui::New);
239 gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().basegame );
240 if ( globalGameComboConfiguration().known[0] != '\0' ) {
241 gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().known );
243 gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().custom );
245 dialog.game_combo.game_select.show();
246 table2.attach(dialog.game_combo.game_select, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
248 dialog.game_combo.game_select.connect( "changed", G_CALLBACK( OnSelchangeComboWhatgame ), &dialog.game_combo );
252 auto label = ui::Label( "fs_game" );
254 table2.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
255 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
258 auto entry = ui::Entry(ui::New);
260 table2.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
262 dialog.game_combo.fsgame_entry = entry;
265 if ( globalMappingMode().do_mapping_mode ) {
266 auto label = ui::Label( "Mapping mode" );
268 table2.attach(label, {0, 1, 3, 4}, {GTK_FILL, 0});
269 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
271 auto combo = ui::ComboBoxText(ui::New);
272 gtk_combo_box_text_append_text( combo, globalMappingMode().sp_mapping_mode );
273 gtk_combo_box_text_append_text( combo, globalMappingMode().mp_mapping_mode );
276 table2.attach(combo, {1, 2, 3, 4}, {GTK_EXPAND | GTK_FILL, 0});
278 dialog.gamemode_combo = combo;
284 // initialise the fs_game selection from the project settings into the dialog
285 const char* dir = gamename_get();
286 gamecombo_t gamecombo = gamecombo_for_dir( dir );
288 gtk_combo_box_set_active( dialog.game_combo.game_select, gamecombo.game );
289 dialog.game_combo.fsgame_entry.text( gamecombo.fs_game );
290 gtk_widget_set_sensitive( dialog.game_combo.fsgame_entry , gamecombo.sensitive );
292 if ( globalMappingMode().do_mapping_mode ) {
293 const char *gamemode = gamemode_get();
294 if ( string_empty( gamemode ) || string_equal( gamemode, "sp" ) ) {
295 gtk_combo_box_set_active( dialog.gamemode_combo, 0 );
299 gtk_combo_box_set_active( dialog.gamemode_combo, 1 );
306 void ProjectSettingsDialog_ok( ProjectSettingsDialog& dialog ){
307 const char* dir = gtk_entry_get_text( dialog.game_combo.fsgame_entry );
309 const char* new_gamename = path_equal( dir, globalGameComboConfiguration().basegame_dir )
313 if ( !path_equal( new_gamename, gamename_get() ) ) {
314 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Changing Game Name" );
316 EnginePath_Unrealise();
318 gamename_set( new_gamename );
320 EnginePath_Realise();
323 if ( globalMappingMode().do_mapping_mode ) {
324 // read from gamemode_combo
325 int active = gtk_combo_box_get_active( dialog.gamemode_combo );
326 if ( active == -1 || active == 0 ) {
327 gamemode_set( "sp" );
331 gamemode_set( "mp" );
336 void DoProjectSettings(){
337 if ( ConfirmModified( "Edit Project Settings" ) ) {
339 ProjectSettingsDialog dialog;
341 ui::Window window = ProjectSettingsDialog_construct( dialog, modal );
343 if ( modal_dialog_show( window, modal ) == eIDOK ) {
344 ProjectSettingsDialog_ok( dialog );
351 // =============================================================================
352 // Arbitrary Sides dialog
354 void DoSides( int type, int axis ){
356 GtkEntry* sides_entry;
358 auto window = MainFrame_getWindow().create_dialog_window("Arbitrary sides", G_CALLBACK(dialog_delete_callback ), &dialog );
360 auto accel = ui::AccelGroup(ui::New);
361 window.add_accel_group( accel );
364 auto hbox = create_dialog_hbox( 4, 4 );
367 auto label = ui::Label( "Sides:" );
369 hbox.pack_start( label, FALSE, FALSE, 0 );
372 auto entry = ui::Entry(ui::New);
374 hbox.pack_start( entry, FALSE, FALSE, 0 );
376 gtk_widget_grab_focus( entry );
379 auto vbox = create_dialog_vbox( 4 );
380 hbox.pack_start( vbox, TRUE, TRUE, 0 );
382 auto button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &dialog );
383 vbox.pack_start( button, FALSE, FALSE, 0 );
384 widget_make_default( button );
385 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
388 auto button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &dialog );
389 vbox.pack_start( button, FALSE, FALSE, 0 );
390 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
395 if ( modal_dialog_show( window, dialog ) == eIDOK ) {
396 const char *str = gtk_entry_get_text( sides_entry );
398 Scene_BrushConstructPrefab( GlobalSceneGraph(), (EBrushPrefab)type, atoi( str ), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
404 // =============================================================================
405 // About dialog (no program is complete without one)
407 void about_button_changelog( ui::Widget widget, gpointer data ){
408 StringOutputStream log( 256 );
409 log << "https://gitlab.com/xonotic/netradiant/commits/master";
410 OpenURL( log.c_str() );
413 void about_button_credits( ui::Widget widget, gpointer data ){
414 StringOutputStream cred( 256 );
415 cred << "https://gitlab.com/xonotic/netradiant/graphs/master";
416 OpenURL( cred.c_str() );
419 void about_button_issues( GtkWidget *widget, gpointer data ){
420 StringOutputStream cred( 256 );
421 cred << "https://gitlab.com/xonotic/netradiant/issues";
422 OpenURL( cred.c_str() );
427 ModalDialogButton ok_button( dialog, eIDOK );
429 auto window = MainFrame_getWindow().create_modal_dialog_window("About NetRadiant", dialog );
432 auto vbox = create_dialog_vbox( 4, 4 );
436 auto hbox = create_dialog_hbox( 4 );
437 vbox.pack_start( hbox, FALSE, TRUE, 0 );
440 auto vbox2 = create_dialog_vbox( 4 );
441 hbox.pack_start( vbox2, TRUE, FALSE, 0 );
443 auto frame = create_dialog_frame( 0, ui::Shadow::IN );
444 vbox2.pack_start( frame, FALSE, FALSE, 0 );
446 auto image = new_local_image( "logo.png" );
454 char const *label_text = "NetRadiant " RADIANT_VERSION "\n"
456 RADIANT_ABOUTMSG "\n\n"
457 "This program is free software\n"
458 "licensed under the GNU GPL.\n\n"
459 "NetRadiant is unsupported, however\n"
460 "you may report your problems at\n"
461 "https://gitlab.com/xonotic/netradiant/issues";
463 auto label = ui::Label( label_text );
466 hbox.pack_start( label, FALSE, FALSE, 0 );
467 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
468 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
472 auto vbox2 = create_dialog_vbox( 4 );
473 hbox.pack_start( vbox2, FALSE, TRUE, 0 );
475 auto button = create_modal_dialog_button( "OK", ok_button );
476 vbox2.pack_start( button, FALSE, FALSE, 0 );
479 auto button = create_dialog_button( "Credits", G_CALLBACK( about_button_credits ), 0 );
480 vbox2.pack_start( button, FALSE, FALSE, 0 );
483 auto button = create_dialog_button( "Changes", G_CALLBACK( about_button_changelog ), 0 );
484 vbox2.pack_start( button, FALSE, FALSE, 0 );
487 auto button = create_dialog_button( "Issues", G_CALLBACK( about_button_issues ), 0 );
488 vbox2.pack_start( button, FALSE, FALSE, 0 );
493 auto frame = create_dialog_frame( "OpenGL Properties" );
494 vbox.pack_start( frame, FALSE, FALSE, 0 );
496 auto table = create_dialog_table( 3, 2, 4, 4, 4 );
499 auto label = ui::Label( "Vendor:" );
501 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
502 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
505 auto label = ui::Label( "Version:" );
507 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
508 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
511 auto label = ui::Label( "Renderer:" );
513 table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0});
514 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
517 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VENDOR ) ) );
519 table.attach(label, {1, 2, 0, 1}, {GTK_FILL, 0});
520 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
523 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VERSION ) ) );
525 table.attach(label, {1, 2, 1, 2}, {GTK_FILL, 0});
526 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
529 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_RENDERER ) ) );
531 table.attach(label, {1, 2, 2, 3}, {GTK_FILL, 0});
532 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
536 auto frame = create_dialog_frame( "OpenGL Extensions" );
537 vbox.pack_start( frame, TRUE, TRUE, 0 );
539 auto sc_extensions = create_scrolled_window( ui::Policy::AUTOMATIC, ui::Policy::ALWAYS, 4 );
540 frame.add(sc_extensions);
542 auto text_extensions = ui::TextView(ui::New);
543 gtk_text_view_set_editable( text_extensions, FALSE );
544 sc_extensions.add(text_extensions);
545 text_extensions.text(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)));
546 gtk_text_view_set_wrap_mode( text_extensions, GTK_WRAP_WORD );
547 text_extensions.show();
554 modal_dialog_show( window, dialog );
559 // =============================================================================
560 // TextureLayout dialog
562 // Last used texture scale values
563 static float last_used_texture_layout_scale_x = 4.0;
564 static float last_used_texture_layout_scale_y = 4.0;
566 EMessageBoxReturn DoTextureLayout( float *fx, float *fy ){
568 ModalDialogButton ok_button( dialog, eIDOK );
569 ModalDialogButton cancel_button( dialog, eIDCANCEL );
570 ui::Entry x{ui::null};
571 ui::Entry y{ui::null};
573 auto window = MainFrame_getWindow().create_modal_dialog_window("Patch texture layout", dialog );
575 auto accel = ui::AccelGroup(ui::New);
576 window.add_accel_group( accel );
579 auto hbox = create_dialog_hbox( 4, 4 );
582 auto vbox = create_dialog_vbox( 4 );
583 hbox.pack_start( vbox, TRUE, TRUE, 0 );
585 auto label = ui::Label( "Texture will be fit across the patch based\n"
586 "on the x and y values given. Values of 1x1\n"
587 "will \"fit\" the texture. 2x2 will repeat\n"
590 vbox.pack_start( label, TRUE, TRUE, 0 );
591 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
594 auto table = create_dialog_table( 2, 2, 4, 4 );
596 vbox.pack_start( table, TRUE, TRUE, 0 );
598 auto label = ui::Label( "Texture x:" );
600 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
601 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
604 auto label = ui::Label( "Texture y:" );
606 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
607 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
610 auto entry = ui::Entry(ui::New);
612 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
617 auto entry = ui::Entry(ui::New);
619 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
626 auto vbox = create_dialog_vbox( 4 );
627 hbox.pack_start( vbox, FALSE, FALSE, 0 );
629 auto button = create_modal_dialog_button( "OK", ok_button );
630 vbox.pack_start( button, FALSE, FALSE, 0 );
631 widget_make_default( button );
632 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
635 auto button = create_modal_dialog_button( "Cancel", cancel_button );
636 vbox.pack_start( button, FALSE, FALSE, 0 );
637 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
642 // Initialize with last used values
645 sprintf( buf, "%f", last_used_texture_layout_scale_x );
648 sprintf( buf, "%f", last_used_texture_layout_scale_y );
651 // Set focus after intializing the values
652 gtk_widget_grab_focus( x );
654 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
655 if ( ret == eIDOK ) {
656 *fx = static_cast<float>( atof( gtk_entry_get_text( x ) ) );
657 *fy = static_cast<float>( atof( gtk_entry_get_text( y ) ) );
659 // Remember last used values
660 last_used_texture_layout_scale_x = *fx;
661 last_used_texture_layout_scale_y = *fy;
669 // =============================================================================
670 // Text Editor dialog
672 // master window widget
673 static ui::Window text_editor{ui::null};
674 static ui::Widget text_widget{ui::null}; // slave, text widget from the gtk editor
676 static gint editor_delete( ui::Widget widget, gpointer data ){
677 if ( widget.window().alert( "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
686 static void editor_save( ui::Widget widget, gpointer data ){
687 FILE *f = fopen( (char*)g_object_get_data( G_OBJECT( data ), "filename" ), "w" );
688 gpointer text = g_object_get_data( G_OBJECT( data ), "text" );
691 ui::Widget::from(data).window().alert( "Error saving file !" );
695 char *str = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
696 fwrite( str, 1, strlen( str ), f );
700 static void editor_close( ui::Widget widget, gpointer data ){
701 if ( text_editor.window().alert( "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
708 static void CreateGtkTextEditor(){
709 auto dlg = ui::Window( ui::window_type::TOP );
711 dlg.connect( "delete_event",
712 G_CALLBACK( editor_delete ), 0 );
713 gtk_window_set_default_size( dlg, 600, 300 );
715 auto vbox = ui::VBox( FALSE, 5 );
718 gtk_container_set_border_width( GTK_CONTAINER( vbox ), 5 );
720 auto scr = ui::ScrolledWindow(ui::New);
722 vbox.pack_start( scr, TRUE, TRUE, 0 );
723 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scr ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
724 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( scr ), GTK_SHADOW_IN );
726 auto text = ui::TextView(ui::New);
729 g_object_set_data( G_OBJECT( dlg ), "text", (gpointer) text );
730 gtk_text_view_set_editable( text, TRUE );
732 auto hbox = ui::HBox( FALSE, 5 );
734 vbox.pack_start( hbox, FALSE, TRUE, 0 );
736 auto button = ui::Button( "Close" );
738 hbox.pack_end(button, FALSE, FALSE, 0);
739 button.connect( "clicked",
740 G_CALLBACK( editor_close ), dlg );
741 gtk_widget_set_size_request( button, 60, -1 );
743 button = ui::Button( "Save" );
745 hbox.pack_end(button, FALSE, FALSE, 0);
746 button.connect( "clicked",
747 G_CALLBACK( editor_save ), dlg );
748 gtk_widget_set_size_request( button, 60, -1 );
754 static void DoGtkTextEditor( const char* filename, guint cursorpos ){
755 if ( !text_editor ) {
756 CreateGtkTextEditor(); // build it the first time we need it
760 FILE *f = fopen( filename, "r" );
763 globalOutputStream() << "Unable to load file " << filename << " in shader editor.\n";
768 fseek( f, 0, SEEK_END );
769 int len = ftell( f );
770 void *buf = malloc( len );
774 fread( buf, 1, len, f );
776 gtk_window_set_title( text_editor, filename );
778 GtkTextBuffer *text_buffer = gtk_text_view_get_buffer(ui::TextView::from(text_widget));
779 gtk_text_buffer_set_text( text_buffer, (char*)buf, len );
781 old_filename = g_object_get_data( G_OBJECT( text_editor ), "filename" );
782 if ( old_filename ) {
783 free( old_filename );
785 g_object_set_data( G_OBJECT( text_editor ), "filename", strdup( filename ) );
787 // trying to show later
794 // only move the cursor if it's not exceeding the size..
795 // NOTE: this is erroneous, cursorpos is the offset in bytes, not in characters
796 // len is the max size in bytes, not in characters either, but the character count is below that limit..
797 // thinking .. the difference between character count and byte count would be only because of CR/LF?
799 GtkTextIter text_iter;
800 // character offset, not byte offset
801 gtk_text_buffer_get_iter_at_offset( text_buffer, &text_iter, cursorpos );
802 gtk_text_buffer_place_cursor( text_buffer, &text_iter );
806 gtk_widget_queue_draw( text_widget );
814 // =============================================================================
815 // Light Intensity dialog
817 EMessageBoxReturn DoLightIntensityDlg( int *intensity ){
819 ui::Entry intensity_entry{ui::null};
820 ModalDialogButton ok_button( dialog, eIDOK );
821 ModalDialogButton cancel_button( dialog, eIDCANCEL );
823 ui::Window window = MainFrame_getWindow().create_modal_dialog_window("Light intensity", dialog, -1, -1 );
825 auto accel_group = ui::AccelGroup(ui::New);
826 window.add_accel_group( accel_group );
829 auto hbox = create_dialog_hbox( 4, 4 );
832 auto vbox = create_dialog_vbox( 4 );
833 hbox.pack_start( vbox, TRUE, TRUE, 0 );
835 auto label = ui::Label( "ESC for default, ENTER to validate" );
837 vbox.pack_start( label, FALSE, FALSE, 0 );
840 auto entry = ui::Entry(ui::New);
842 vbox.pack_start( entry, TRUE, TRUE, 0 );
844 gtk_widget_grab_focus( entry );
846 intensity_entry = entry;
850 auto vbox = create_dialog_vbox( 4 );
851 hbox.pack_start( vbox, FALSE, FALSE, 0 );
854 auto button = create_modal_dialog_button( "OK", ok_button );
855 vbox.pack_start( button, FALSE, FALSE, 0 );
856 widget_make_default( button );
857 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
860 auto button = create_modal_dialog_button( "Cancel", cancel_button );
861 vbox.pack_start( button, FALSE, FALSE, 0 );
862 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
868 sprintf( buf, "%d", *intensity );
869 intensity_entry.text(buf);
871 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
872 if ( ret == eIDOK ) {
873 *intensity = atoi( gtk_entry_get_text( intensity_entry ) );
881 // =============================================================================
882 // Add new shader tag dialog
884 EMessageBoxReturn DoShaderTagDlg( CopiedString* tag, const char* title ){
887 ModalDialogButton ok_button( dialog, eIDOK );
888 ModalDialogButton cancel_button( dialog, eIDCANCEL );
890 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
892 auto accel_group = ui::AccelGroup(ui::New);
893 window.add_accel_group( accel_group );
896 auto hbox = create_dialog_hbox( 4, 4 );
899 auto vbox = create_dialog_vbox( 4 );
900 hbox.pack_start( vbox, TRUE, TRUE, 0 );
902 //GtkLabel* label = GTK_LABEL(gtk_label_new("Enter one ore more tags separated by spaces"));
903 auto label = ui::Label( "ESC to cancel, ENTER to validate" );
905 vbox.pack_start( label, FALSE, FALSE, 0 );
908 auto entry = ui::Entry(ui::New);
910 vbox.pack_start( entry, TRUE, TRUE, 0 );
912 gtk_widget_grab_focus( entry );
918 auto vbox = create_dialog_vbox( 4 );
919 hbox.pack_start( vbox, FALSE, FALSE, 0 );
922 auto button = create_modal_dialog_button( "OK", ok_button );
923 vbox.pack_start( button, FALSE, FALSE, 0 );
924 widget_make_default( button );
925 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
928 auto button = create_modal_dialog_button( "Cancel", cancel_button );
929 vbox.pack_start( button, FALSE, FALSE, 0 );
930 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
935 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
936 if ( ret == eIDOK ) {
937 *tag = gtk_entry_get_text( textentry );
945 EMessageBoxReturn DoShaderInfoDlg( const char* name, const char* filename, const char* title ){
947 ModalDialogButton ok_button( dialog, eIDOK );
949 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
951 auto accel_group = ui::AccelGroup(ui::New);
952 window.add_accel_group( accel_group );
955 auto hbox = create_dialog_hbox( 4, 4 );
958 auto vbox = create_dialog_vbox( 4 );
959 hbox.pack_start( vbox, FALSE, FALSE, 0 );
961 auto label = ui::Label( "The selected shader" );
963 vbox.pack_start( label, FALSE, FALSE, 0 );
966 auto label = ui::Label( name );
968 vbox.pack_start( label, FALSE, FALSE, 0 );
971 auto label = ui::Label( "is located in file" );
973 vbox.pack_start( label, FALSE, FALSE, 0 );
976 auto label = ui::Label( filename );
978 vbox.pack_start( label, FALSE, FALSE, 0 );
981 auto button = create_modal_dialog_button( "OK", ok_button );
982 vbox.pack_start( button, FALSE, FALSE, 0 );
983 widget_make_default( button );
984 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
989 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
999 #include <gdk/gdkwin32.h>
1003 // use the file associations to open files instead of builtin Gtk editor
1004 bool g_TextEditor_useWin32Editor = true;
1006 // custom shader editor
1007 bool g_TextEditor_useCustomEditor = false;
1008 CopiedString g_TextEditor_editorCommand( "" );
1011 void DoTextEditor( const char* filename, int cursorpos ){
1013 if ( g_TextEditor_useWin32Editor ) {
1014 globalOutputStream() << "opening file '" << filename << "' (line " << cursorpos << " info ignored)\n";
1015 ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", filename, 0, 0, SW_SHOW );
1019 // check if a custom editor is set
1020 if ( g_TextEditor_useCustomEditor && !g_TextEditor_editorCommand.empty() ) {
1021 StringOutputStream strEditCommand( 256 );
1022 strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << filename << "\"";
1024 globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n";
1025 // note: linux does not return false if the command failed so it will assume success
1026 if ( Q_Exec( 0, const_cast<char*>( strEditCommand.c_str() ), 0, true, false ) == false ) {
1027 globalOutputStream() << "Failed to execute " << strEditCommand.c_str() << ", using default\n";
1031 // the command (appeared) to run successfully, no need to do anything more
1037 DoGtkTextEditor( filename, cursorpos );