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 ui::ComboBox gamemode_combo{ui::null};
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 ){
357 auto window = MainFrame_getWindow().create_dialog_window("Arbitrary sides", G_CALLBACK(dialog_delete_callback ), &dialog );
359 auto accel = ui::AccelGroup(ui::New);
360 window.add_accel_group( accel );
362 auto sides_entry = ui::Entry(ui::New);
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 = sides_entry;
374 hbox.pack_start( entry, FALSE, FALSE, 0 );
375 gtk_widget_grab_focus( entry );
378 auto vbox = create_dialog_vbox( 4 );
379 hbox.pack_start( vbox, TRUE, TRUE, 0 );
381 auto button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &dialog );
382 vbox.pack_start( button, FALSE, FALSE, 0 );
383 widget_make_default( button );
384 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
387 auto button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &dialog );
388 vbox.pack_start( button, FALSE, FALSE, 0 );
389 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
394 if ( modal_dialog_show( window, dialog ) == eIDOK ) {
395 const char *str = gtk_entry_get_text( sides_entry );
397 Scene_BrushConstructPrefab( GlobalSceneGraph(), (EBrushPrefab)type, atoi( str ), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
403 // =============================================================================
404 // About dialog (no program is complete without one)
406 void about_button_changelog( ui::Widget widget, gpointer data ){
407 StringOutputStream log( 256 );
408 log << "https://gitlab.com/xonotic/netradiant/commits/master";
409 OpenURL( log.c_str() );
412 void about_button_credits( ui::Widget widget, gpointer data ){
413 StringOutputStream cred( 256 );
414 cred << "https://gitlab.com/xonotic/netradiant/graphs/master";
415 OpenURL( cred.c_str() );
418 void about_button_issues( ui::Widget widget, gpointer data ){
419 StringOutputStream cred( 256 );
420 cred << "https://gitlab.com/xonotic/netradiant/issues";
421 OpenURL( cred.c_str() );
426 ModalDialogButton ok_button( dialog, eIDOK );
428 auto window = MainFrame_getWindow().create_modal_dialog_window("About NetRadiant", dialog );
431 auto vbox = create_dialog_vbox( 4, 4 );
435 auto hbox = create_dialog_hbox( 4 );
436 vbox.pack_start( hbox, FALSE, TRUE, 0 );
439 auto vbox2 = create_dialog_vbox( 4 );
440 hbox.pack_start( vbox2, TRUE, FALSE, 0 );
442 auto frame = create_dialog_frame( 0, ui::Shadow::IN );
443 vbox2.pack_start( frame, FALSE, FALSE, 0 );
445 auto image = new_local_image( "logo.png" );
453 char const *label_text = "NetRadiant " RADIANT_VERSION "\n"
455 RADIANT_ABOUTMSG "\n\n"
456 "This program is free software\n"
457 "licensed under the GNU GPL.\n\n"
458 "NetRadiant is unsupported, however\n"
459 "you may report your problems at\n"
460 "https://gitlab.com/xonotic/netradiant/issues";
462 auto label = ui::Label( label_text );
465 hbox.pack_start( label, FALSE, FALSE, 0 );
466 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
467 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
471 auto vbox2 = create_dialog_vbox( 4 );
472 hbox.pack_start( vbox2, FALSE, TRUE, 0 );
474 auto button = create_modal_dialog_button( "OK", ok_button );
475 vbox2.pack_start( button, FALSE, FALSE, 0 );
478 auto button = create_dialog_button( "Credits", G_CALLBACK( about_button_credits ), 0 );
479 vbox2.pack_start( button, FALSE, FALSE, 0 );
482 auto button = create_dialog_button( "Changes", G_CALLBACK( about_button_changelog ), 0 );
483 vbox2.pack_start( button, FALSE, FALSE, 0 );
486 auto button = create_dialog_button( "Issues", G_CALLBACK( about_button_issues ), 0 );
487 vbox2.pack_start( button, FALSE, FALSE, 0 );
492 auto frame = create_dialog_frame( "OpenGL Properties" );
493 vbox.pack_start( frame, FALSE, FALSE, 0 );
495 auto table = create_dialog_table( 3, 2, 4, 4, 4 );
498 auto label = ui::Label( "Vendor:" );
500 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
501 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
504 auto label = ui::Label( "Version:" );
506 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
507 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
510 auto label = ui::Label( "Renderer:" );
512 table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0});
513 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
516 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VENDOR ) ) );
518 table.attach(label, {1, 2, 0, 1}, {GTK_FILL, 0});
519 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
522 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VERSION ) ) );
524 table.attach(label, {1, 2, 1, 2}, {GTK_FILL, 0});
525 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
528 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_RENDERER ) ) );
530 table.attach(label, {1, 2, 2, 3}, {GTK_FILL, 0});
531 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
535 auto frame = create_dialog_frame( "OpenGL Extensions" );
536 vbox.pack_start( frame, TRUE, TRUE, 0 );
538 auto sc_extensions = create_scrolled_window( ui::Policy::AUTOMATIC, ui::Policy::ALWAYS, 4 );
539 frame.add(sc_extensions);
541 auto text_extensions = ui::TextView(ui::New);
542 gtk_text_view_set_editable( text_extensions, FALSE );
543 sc_extensions.add(text_extensions);
544 text_extensions.text(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)));
545 gtk_text_view_set_wrap_mode( text_extensions, GTK_WRAP_WORD );
546 text_extensions.show();
553 modal_dialog_show( window, dialog );
558 // =============================================================================
559 // TextureLayout dialog
561 // Last used texture scale values
562 static float last_used_texture_layout_scale_x = 4.0;
563 static float last_used_texture_layout_scale_y = 4.0;
565 EMessageBoxReturn DoTextureLayout( float *fx, float *fy ){
567 ModalDialogButton ok_button( dialog, eIDOK );
568 ModalDialogButton cancel_button( dialog, eIDCANCEL );
569 ui::Entry x{ui::null};
570 ui::Entry y{ui::null};
572 auto window = MainFrame_getWindow().create_modal_dialog_window("Patch texture layout", dialog );
574 auto accel = ui::AccelGroup(ui::New);
575 window.add_accel_group( accel );
578 auto hbox = create_dialog_hbox( 4, 4 );
581 auto vbox = create_dialog_vbox( 4 );
582 hbox.pack_start( vbox, TRUE, TRUE, 0 );
584 auto label = ui::Label( "Texture will be fit across the patch based\n"
585 "on the x and y values given. Values of 1x1\n"
586 "will \"fit\" the texture. 2x2 will repeat\n"
589 vbox.pack_start( label, TRUE, TRUE, 0 );
590 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
593 auto table = create_dialog_table( 2, 2, 4, 4 );
595 vbox.pack_start( table, TRUE, TRUE, 0 );
597 auto label = ui::Label( "Texture x:" );
599 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
600 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
603 auto label = ui::Label( "Texture y:" );
605 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
606 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
609 auto entry = ui::Entry(ui::New);
611 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
616 auto entry = ui::Entry(ui::New);
618 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
625 auto vbox = create_dialog_vbox( 4 );
626 hbox.pack_start( vbox, FALSE, FALSE, 0 );
628 auto button = create_modal_dialog_button( "OK", ok_button );
629 vbox.pack_start( button, FALSE, FALSE, 0 );
630 widget_make_default( button );
631 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
634 auto button = create_modal_dialog_button( "Cancel", cancel_button );
635 vbox.pack_start( button, FALSE, FALSE, 0 );
636 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
641 // Initialize with last used values
644 sprintf( buf, "%f", last_used_texture_layout_scale_x );
647 sprintf( buf, "%f", last_used_texture_layout_scale_y );
650 // Set focus after intializing the values
651 gtk_widget_grab_focus( x );
653 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
654 if ( ret == eIDOK ) {
655 *fx = static_cast<float>( atof( gtk_entry_get_text( x ) ) );
656 *fy = static_cast<float>( atof( gtk_entry_get_text( y ) ) );
658 // Remember last used values
659 last_used_texture_layout_scale_x = *fx;
660 last_used_texture_layout_scale_y = *fy;
668 // =============================================================================
669 // Text Editor dialog
671 // master window widget
672 static ui::Window text_editor{ui::null};
673 static ui::Widget text_widget{ui::null}; // slave, text widget from the gtk editor
675 static gint editor_delete( ui::Widget widget, gpointer data ){
676 if ( ui::alert( widget.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
685 static void editor_save( ui::Widget widget, gpointer data ){
686 FILE *f = fopen( (char*)g_object_get_data( G_OBJECT( data ), "filename" ), "w" );
687 gpointer text = g_object_get_data( G_OBJECT( data ), "text" );
690 ui::alert( ui::Widget::from(data).window(), "Error saving file !" );
694 char *str = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
695 fwrite( str, 1, strlen( str ), f );
699 static void editor_close( ui::Widget widget, gpointer data ){
700 if ( ui::alert( text_editor.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
707 static void CreateGtkTextEditor(){
708 auto dlg = ui::Window( ui::window_type::TOP );
710 dlg.connect( "delete_event",
711 G_CALLBACK( editor_delete ), 0 );
712 gtk_window_set_default_size( dlg, 600, 300 );
714 auto vbox = ui::VBox( FALSE, 5 );
717 gtk_container_set_border_width( GTK_CONTAINER( vbox ), 5 );
719 auto scr = ui::ScrolledWindow(ui::New);
721 vbox.pack_start( scr, TRUE, TRUE, 0 );
722 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scr ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
723 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( scr ), GTK_SHADOW_IN );
725 auto text = ui::TextView(ui::New);
728 g_object_set_data( G_OBJECT( dlg ), "text", (gpointer) text );
729 gtk_text_view_set_editable( text, TRUE );
731 auto hbox = ui::HBox( FALSE, 5 );
733 vbox.pack_start( hbox, FALSE, TRUE, 0 );
735 auto button = ui::Button( "Close" );
737 hbox.pack_end(button, FALSE, FALSE, 0);
738 button.connect( "clicked",
739 G_CALLBACK( editor_close ), dlg );
740 button.dimensions(60, -1);
742 button = ui::Button( "Save" );
744 hbox.pack_end(button, FALSE, FALSE, 0);
745 button.connect( "clicked",
746 G_CALLBACK( editor_save ), dlg );
747 button.dimensions(60, -1);
753 static void DoGtkTextEditor( const char* filename, guint cursorpos ){
754 if ( !text_editor ) {
755 CreateGtkTextEditor(); // build it the first time we need it
759 FILE *f = fopen( filename, "r" );
762 globalOutputStream() << "Unable to load file " << filename << " in shader editor.\n";
767 fseek( f, 0, SEEK_END );
768 int len = ftell( f );
769 void *buf = malloc( len );
773 fread( buf, 1, len, f );
775 gtk_window_set_title( text_editor, filename );
777 auto text_buffer = gtk_text_view_get_buffer(ui::TextView::from(text_widget));
778 gtk_text_buffer_set_text( text_buffer, (char*)buf, len );
780 old_filename = g_object_get_data( G_OBJECT( text_editor ), "filename" );
781 if ( old_filename ) {
782 free( old_filename );
784 g_object_set_data( G_OBJECT( text_editor ), "filename", strdup( filename ) );
786 // trying to show later
793 // only move the cursor if it's not exceeding the size..
794 // NOTE: this is erroneous, cursorpos is the offset in bytes, not in characters
795 // len is the max size in bytes, not in characters either, but the character count is below that limit..
796 // thinking .. the difference between character count and byte count would be only because of CR/LF?
798 GtkTextIter text_iter;
799 // character offset, not byte offset
800 gtk_text_buffer_get_iter_at_offset( text_buffer, &text_iter, cursorpos );
801 gtk_text_buffer_place_cursor( text_buffer, &text_iter );
805 gtk_widget_queue_draw( text_widget );
813 // =============================================================================
814 // Light Intensity dialog
816 EMessageBoxReturn DoLightIntensityDlg( int *intensity ){
818 ui::Entry intensity_entry{ui::null};
819 ModalDialogButton ok_button( dialog, eIDOK );
820 ModalDialogButton cancel_button( dialog, eIDCANCEL );
822 ui::Window window = MainFrame_getWindow().create_modal_dialog_window("Light intensity", dialog, -1, -1 );
824 auto accel_group = ui::AccelGroup(ui::New);
825 window.add_accel_group( accel_group );
828 auto hbox = create_dialog_hbox( 4, 4 );
831 auto vbox = create_dialog_vbox( 4 );
832 hbox.pack_start( vbox, TRUE, TRUE, 0 );
834 auto label = ui::Label( "ESC for default, ENTER to validate" );
836 vbox.pack_start( label, FALSE, FALSE, 0 );
839 auto entry = ui::Entry(ui::New);
841 vbox.pack_start( entry, TRUE, TRUE, 0 );
843 gtk_widget_grab_focus( entry );
845 intensity_entry = entry;
849 auto vbox = create_dialog_vbox( 4 );
850 hbox.pack_start( vbox, FALSE, FALSE, 0 );
853 auto button = create_modal_dialog_button( "OK", ok_button );
854 vbox.pack_start( button, FALSE, FALSE, 0 );
855 widget_make_default( button );
856 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
859 auto button = create_modal_dialog_button( "Cancel", cancel_button );
860 vbox.pack_start( button, FALSE, FALSE, 0 );
861 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
867 sprintf( buf, "%d", *intensity );
868 intensity_entry.text(buf);
870 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
871 if ( ret == eIDOK ) {
872 *intensity = atoi( gtk_entry_get_text( intensity_entry ) );
880 // =============================================================================
881 // Add new shader tag dialog
883 EMessageBoxReturn DoShaderTagDlg( CopiedString* tag, const char* title ){
885 ModalDialogButton ok_button( dialog, eIDOK );
886 ModalDialogButton cancel_button( dialog, eIDCANCEL );
888 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
890 auto accel_group = ui::AccelGroup(ui::New);
891 window.add_accel_group( accel_group );
893 auto textentry = ui::Entry(ui::New);
895 auto hbox = create_dialog_hbox( 4, 4 );
898 auto vbox = create_dialog_vbox( 4 );
899 hbox.pack_start( vbox, TRUE, TRUE, 0 );
901 //GtkLabel* label = GTK_LABEL(gtk_label_new("Enter one ore more tags separated by spaces"));
902 auto label = ui::Label( "ESC to cancel, ENTER to validate" );
904 vbox.pack_start( label, FALSE, FALSE, 0 );
907 auto entry = textentry;
909 vbox.pack_start( entry, TRUE, TRUE, 0 );
911 gtk_widget_grab_focus( entry );
915 auto vbox = create_dialog_vbox( 4 );
916 hbox.pack_start( vbox, FALSE, FALSE, 0 );
919 auto button = create_modal_dialog_button( "OK", ok_button );
920 vbox.pack_start( button, FALSE, FALSE, 0 );
921 widget_make_default( button );
922 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
925 auto button = create_modal_dialog_button( "Cancel", cancel_button );
926 vbox.pack_start( button, FALSE, FALSE, 0 );
927 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
932 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
933 if ( ret == eIDOK ) {
934 *tag = gtk_entry_get_text( textentry );
942 EMessageBoxReturn DoShaderInfoDlg( const char* name, const char* filename, const char* title ){
944 ModalDialogButton ok_button( dialog, eIDOK );
946 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
948 auto accel_group = ui::AccelGroup(ui::New);
949 window.add_accel_group( accel_group );
952 auto hbox = create_dialog_hbox( 4, 4 );
955 auto vbox = create_dialog_vbox( 4 );
956 hbox.pack_start( vbox, FALSE, FALSE, 0 );
958 auto label = ui::Label( "The selected shader" );
960 vbox.pack_start( label, FALSE, FALSE, 0 );
963 auto label = ui::Label( name );
965 vbox.pack_start( label, FALSE, FALSE, 0 );
968 auto label = ui::Label( "is located in file" );
970 vbox.pack_start( label, FALSE, FALSE, 0 );
973 auto label = ui::Label( filename );
975 vbox.pack_start( label, FALSE, FALSE, 0 );
978 auto button = create_modal_dialog_button( "OK", ok_button );
979 vbox.pack_start( button, FALSE, FALSE, 0 );
980 widget_make_default( button );
981 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
986 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
996 #include <gdk/gdkwin32.h>
1000 // use the file associations to open files instead of builtin Gtk editor
1001 bool g_TextEditor_useWin32Editor = true;
1003 // custom shader editor
1004 bool g_TextEditor_useCustomEditor = false;
1005 CopiedString g_TextEditor_editorCommand( "" );
1008 void DoTextEditor( const char* filename, int cursorpos ){
1010 if ( g_TextEditor_useWin32Editor ) {
1011 globalOutputStream() << "opening file '" << filename << "' (line " << cursorpos << " info ignored)\n";
1012 ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", filename, 0, 0, SW_SHOW );
1016 // check if a custom editor is set
1017 if ( g_TextEditor_useCustomEditor && !g_TextEditor_editorCommand.empty() ) {
1018 StringOutputStream strEditCommand( 256 );
1019 strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << filename << "\"";
1021 globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n";
1022 // note: linux does not return false if the command failed so it will assume success
1023 if ( Q_Exec( 0, const_cast<char*>( strEditCommand.c_str() ), 0, true, false ) == false ) {
1024 globalOutputStream() << "Failed to execute " << strEditCommand.c_str() << ", using default\n";
1028 // the command (appeared) to run successfully, no need to do anything more
1034 DoGtkTextEditor( filename, cursorpos );