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"
73 #include "qerplugin.h"
78 // =============================================================================
79 // Project settings dialog
81 class GameComboConfiguration
84 const char* basegame_dir;
86 const char* known_dir;
90 GameComboConfiguration() :
91 basegame_dir( g_pGameDescription->getRequiredKeyValue( "basegame" ) ),
92 basegame( g_pGameDescription->getRequiredKeyValue( "basegamename" ) ),
93 known_dir( g_pGameDescription->getKeyValue( "knowngame" ) ),
94 known( g_pGameDescription->getKeyValue( "knowngamename" ) ),
95 custom( g_pGameDescription->getRequiredKeyValue( "unknowngamename" ) ){
99 typedef LazyStatic<GameComboConfiguration> LazyStaticGameComboConfiguration;
101 inline GameComboConfiguration& globalGameComboConfiguration(){
102 return LazyStaticGameComboConfiguration::instance();
108 gamecombo_t( int _game, const char* _fs_game, bool _sensitive )
109 : game( _game ), fs_game( _fs_game ), sensitive( _sensitive )
116 gamecombo_t gamecombo_for_dir( const char* dir ){
117 if ( string_equal( dir, globalGameComboConfiguration().basegame_dir ) ) {
118 return gamecombo_t( 0, "", false );
120 else if ( string_equal( dir, globalGameComboConfiguration().known_dir ) ) {
121 return gamecombo_t( 1, dir, false );
125 return gamecombo_t( string_empty( globalGameComboConfiguration().known_dir ) ? 1 : 2, dir, true );
129 gamecombo_t gamecombo_for_gamename( const char* gamename ){
130 if ( ( strlen( gamename ) == 0 ) || !strcmp( gamename, globalGameComboConfiguration().basegame ) ) {
131 return gamecombo_t( 0, "", false );
133 else if ( !strcmp( gamename, globalGameComboConfiguration().known ) ) {
134 return gamecombo_t( 1, globalGameComboConfiguration().known_dir, false );
138 return gamecombo_t( string_empty( globalGameComboConfiguration().known_dir ) ? 1 : 2, "", true );
142 inline void path_copy_clean( char* destination, const char* source ){
143 char* i = destination;
145 while ( *source != '\0' )
147 *i++ = ( *source == '\\' ) ? '/' : *source;
151 if ( i != destination && *( i - 1 ) != '/' ) {
161 ui::ComboBoxText game_select{ui::null};
162 ui::Entry fsgame_entry{ui::null};
165 gboolean OnSelchangeComboWhatgame( ui::Widget widget, GameCombo* combo ){
166 const char *gamename;
169 gtk_combo_box_get_active_iter( combo->game_select, &iter );
170 gtk_tree_model_get( gtk_combo_box_get_model( combo->game_select ), &iter, 0, (gpointer*)&gamename, -1 );
173 gamecombo_t gamecombo = gamecombo_for_gamename( gamename );
175 combo->fsgame_entry.text( gamecombo.fs_game );
176 gtk_widget_set_sensitive( combo->fsgame_entry , gamecombo.sensitive );
184 bool do_mapping_mode;
185 const char* sp_mapping_mode;
186 const char* mp_mapping_mode;
189 do_mapping_mode( !string_empty( g_pGameDescription->getKeyValue( "show_gamemode" ) ) ),
190 sp_mapping_mode( "Single Player mapping mode" ),
191 mp_mapping_mode( "Multiplayer mapping mode" ){
195 typedef LazyStatic<MappingMode> LazyStaticMappingMode;
197 inline MappingMode& globalMappingMode(){
198 return LazyStaticMappingMode::instance();
201 class ProjectSettingsDialog
204 GameCombo game_combo;
205 ui::ComboBox gamemode_combo{ui::null};
208 ui::Window ProjectSettingsDialog_construct( ProjectSettingsDialog& dialog, ModalDialog& modal ){
209 auto window = MainFrame_getWindow().create_dialog_window("Project Settings", G_CALLBACK(dialog_delete_callback ), &modal );
212 auto table1 = create_dialog_table( 1, 2, 4, 4, 4 );
215 auto vbox = create_dialog_vbox( 4 );
216 table1.attach(vbox, {1, 2, 0, 1}, {GTK_FILL, GTK_FILL});
218 auto button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &modal );
219 vbox.pack_start( button, FALSE, FALSE, 0 );
222 auto button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &modal );
223 vbox.pack_start( button, FALSE, FALSE, 0 );
227 auto frame = create_dialog_frame( "Project settings" );
228 table1.attach(frame, {0, 1, 0, 1}, {GTK_EXPAND | GTK_FILL, GTK_FILL});
230 auto table2 = create_dialog_table( ( globalMappingMode().do_mapping_mode ) ? 4 : 3, 2, 4, 4, 4 );
234 auto label = ui::Label( "Select mod" );
236 table2.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
237 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
240 dialog.game_combo.game_select = ui::ComboBoxText(ui::New);
242 gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().basegame );
243 if ( globalGameComboConfiguration().known[0] != '\0' ) {
244 gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().known );
246 gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().custom );
248 dialog.game_combo.game_select.show();
249 table2.attach(dialog.game_combo.game_select, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
251 dialog.game_combo.game_select.connect( "changed", G_CALLBACK( OnSelchangeComboWhatgame ), &dialog.game_combo );
255 auto label = ui::Label( "fs_game" );
257 table2.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
258 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
261 auto entry = ui::Entry(ui::New);
263 table2.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
265 dialog.game_combo.fsgame_entry = entry;
268 if ( globalMappingMode().do_mapping_mode ) {
269 auto label = ui::Label( "Mapping mode" );
271 table2.attach(label, {0, 1, 3, 4}, {GTK_FILL, 0});
272 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
274 auto combo = ui::ComboBoxText(ui::New);
275 gtk_combo_box_text_append_text( combo, globalMappingMode().sp_mapping_mode );
276 gtk_combo_box_text_append_text( combo, globalMappingMode().mp_mapping_mode );
279 table2.attach(combo, {1, 2, 3, 4}, {GTK_EXPAND | GTK_FILL, 0});
281 dialog.gamemode_combo = combo;
287 // initialise the fs_game selection from the project settings into the dialog
288 const char* dir = gamename_get();
289 gamecombo_t gamecombo = gamecombo_for_dir( dir );
291 gtk_combo_box_set_active( dialog.game_combo.game_select, gamecombo.game );
292 dialog.game_combo.fsgame_entry.text( gamecombo.fs_game );
293 gtk_widget_set_sensitive( dialog.game_combo.fsgame_entry , gamecombo.sensitive );
295 if ( globalMappingMode().do_mapping_mode ) {
296 const char *gamemode = gamemode_get();
297 if ( string_empty( gamemode ) || string_equal( gamemode, "sp" ) ) {
298 gtk_combo_box_set_active( dialog.gamemode_combo, 0 );
302 gtk_combo_box_set_active( dialog.gamemode_combo, 1 );
309 void ProjectSettingsDialog_ok( ProjectSettingsDialog& dialog ){
310 const char* dir = gtk_entry_get_text( dialog.game_combo.fsgame_entry );
312 const char* new_gamename = path_equal( dir, globalGameComboConfiguration().basegame_dir )
316 if ( !path_equal( new_gamename, gamename_get() ) ) {
317 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Changing Game Name" );
319 EnginePath_Unrealise();
321 gamename_set( new_gamename );
323 EnginePath_Realise();
326 if ( globalMappingMode().do_mapping_mode ) {
327 // read from gamemode_combo
328 int active = gtk_combo_box_get_active( dialog.gamemode_combo );
329 if ( active == -1 || active == 0 ) {
330 gamemode_set( "sp" );
334 gamemode_set( "mp" );
339 void DoProjectSettings(){
340 //if ( ConfirmModified( "Edit Project Settings" ) ) {
342 ProjectSettingsDialog dialog;
344 ui::Window window = ProjectSettingsDialog_construct( dialog, modal );
346 if ( modal_dialog_show( window, modal ) == eIDOK ) {
347 ProjectSettingsDialog_ok( dialog );
354 // =============================================================================
355 // Arbitrary Sides dialog
357 void DoSides( int type, int axis ){
360 auto window = MainFrame_getWindow().create_dialog_window("Arbitrary sides", G_CALLBACK(dialog_delete_callback ), &dialog );
362 auto accel = ui::AccelGroup(ui::New);
363 window.add_accel_group( accel );
365 auto sides_entry = ui::Entry(ui::New);
367 auto hbox = create_dialog_hbox( 4, 4 );
370 auto label = ui::Label( "Sides:" );
372 hbox.pack_start( label, FALSE, FALSE, 0 );
375 auto entry = sides_entry;
377 hbox.pack_start( entry, FALSE, FALSE, 0 );
378 gtk_widget_grab_focus( entry );
381 auto vbox = create_dialog_vbox( 4 );
382 hbox.pack_start( vbox, TRUE, TRUE, 0 );
384 auto button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &dialog );
385 vbox.pack_start( button, FALSE, FALSE, 0 );
386 widget_make_default( button );
387 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
390 auto button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &dialog );
391 vbox.pack_start( button, FALSE, FALSE, 0 );
392 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
397 if ( modal_dialog_show( window, dialog ) == eIDOK ) {
398 const char *str = gtk_entry_get_text( sides_entry );
400 Scene_BrushConstructPrefab( GlobalSceneGraph(), (EBrushPrefab)type, atoi( str ), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
406 // =============================================================================
407 // About dialog (no program is complete without one)
409 void about_button_changelog( ui::Widget widget, gpointer data ){
410 StringOutputStream log( 256 );
411 log << "https://gitlab.com/xonotic/netradiant/commits/master";
412 OpenURL( log.c_str() );
415 void about_button_credits( ui::Widget widget, gpointer data ){
416 StringOutputStream cred( 256 );
417 cred << "https://gitlab.com/xonotic/netradiant/graphs/master";
418 OpenURL( cred.c_str() );
421 void about_button_issues( ui::Widget widget, gpointer data ){
422 StringOutputStream cred( 256 );
423 cred << "https://gitlab.com/xonotic/netradiant/issues";
424 OpenURL( cred.c_str() );
429 ModalDialogButton ok_button( dialog, eIDOK );
431 auto window = MainFrame_getWindow().create_modal_dialog_window("About NetRadiant", dialog );
434 auto vbox = create_dialog_vbox( 4, 4 );
438 auto hbox = create_dialog_hbox( 4 );
439 vbox.pack_start( hbox, FALSE, TRUE, 0 );
442 auto vbox2 = create_dialog_vbox( 4 );
443 hbox.pack_start( vbox2, TRUE, FALSE, 0 );
445 auto frame = create_dialog_frame( 0, ui::Shadow::IN );
446 vbox2.pack_start( frame, FALSE, FALSE, 0 );
448 auto image = new_local_image( "logo.png" );
456 char const *label_text = "NetRadiant " RADIANT_VERSION "\n"
458 RADIANT_ABOUTMSG "\n\n"
459 "This program is free software\n"
460 "licensed under the GNU GPL.\n\n"
461 "NetRadiant is unsupported, however\n"
462 "you may report your problems at\n"
463 "https://gitlab.com/xonotic/netradiant/issues";
465 auto label = ui::Label( label_text );
468 hbox.pack_start( label, FALSE, FALSE, 0 );
469 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
470 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
474 auto vbox2 = create_dialog_vbox( 4 );
475 hbox.pack_start( vbox2, FALSE, TRUE, 0 );
477 auto button = create_modal_dialog_button( "OK", ok_button );
478 vbox2.pack_start( button, FALSE, FALSE, 0 );
481 auto button = create_dialog_button( "Credits", G_CALLBACK( about_button_credits ), 0 );
482 vbox2.pack_start( button, FALSE, FALSE, 0 );
485 auto button = create_dialog_button( "Changes", G_CALLBACK( about_button_changelog ), 0 );
486 vbox2.pack_start( button, FALSE, FALSE, 0 );
489 auto button = create_dialog_button( "Issues", G_CALLBACK( about_button_issues ), 0 );
490 vbox2.pack_start( button, FALSE, FALSE, 0 );
495 auto frame = create_dialog_frame( "OpenGL Properties" );
496 vbox.pack_start( frame, FALSE, FALSE, 0 );
498 auto table = create_dialog_table( 3, 2, 4, 4, 4 );
501 auto label = ui::Label( "Vendor:" );
503 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
504 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
507 auto label = ui::Label( "Version:" );
509 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
510 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
513 auto label = ui::Label( "Renderer:" );
515 table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0});
516 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
519 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VENDOR ) ) );
521 table.attach(label, {1, 2, 0, 1}, {GTK_FILL, 0});
522 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
525 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VERSION ) ) );
527 table.attach(label, {1, 2, 1, 2}, {GTK_FILL, 0});
528 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
531 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_RENDERER ) ) );
533 table.attach(label, {1, 2, 2, 3}, {GTK_FILL, 0});
534 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
538 auto frame = create_dialog_frame( "OpenGL Extensions" );
539 vbox.pack_start( frame, TRUE, TRUE, 0 );
541 auto sc_extensions = create_scrolled_window( ui::Policy::AUTOMATIC, ui::Policy::ALWAYS, 4 );
542 frame.add(sc_extensions);
544 auto text_extensions = ui::TextView(ui::New);
545 gtk_text_view_set_editable( text_extensions, FALSE );
546 sc_extensions.add(text_extensions);
547 text_extensions.text(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)));
548 gtk_text_view_set_wrap_mode( text_extensions, GTK_WRAP_WORD );
549 text_extensions.show();
556 modal_dialog_show( window, dialog );
561 // =============================================================================
562 // TextureLayout dialog
564 // Last used texture scale values
565 static float last_used_texture_layout_scale_x = 4.0;
566 static float last_used_texture_layout_scale_y = 4.0;
568 EMessageBoxReturn DoTextureLayout( float *fx, float *fy ){
570 ModalDialogButton ok_button( dialog, eIDOK );
571 ModalDialogButton cancel_button( dialog, eIDCANCEL );
572 ui::Entry x{ui::null};
573 ui::Entry y{ui::null};
575 auto window = MainFrame_getWindow().create_modal_dialog_window("Patch texture layout", dialog );
577 auto accel = ui::AccelGroup(ui::New);
578 window.add_accel_group( accel );
581 auto hbox = create_dialog_hbox( 4, 4 );
584 auto vbox = create_dialog_vbox( 4 );
585 hbox.pack_start( vbox, TRUE, TRUE, 0 );
587 auto label = ui::Label( "Texture will be fit across the patch based\n"
588 "on the x and y values given. Values of 1x1\n"
589 "will \"fit\" the texture. 2x2 will repeat\n"
592 vbox.pack_start( label, TRUE, TRUE, 0 );
593 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
596 auto table = create_dialog_table( 2, 2, 4, 4 );
598 vbox.pack_start( table, TRUE, TRUE, 0 );
600 auto label = ui::Label( "Texture x:" );
602 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
603 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
606 auto label = ui::Label( "Texture y:" );
608 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
609 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
612 auto entry = ui::Entry(ui::New);
614 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
619 auto entry = ui::Entry(ui::New);
621 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
628 auto vbox = create_dialog_vbox( 4 );
629 hbox.pack_start( vbox, FALSE, FALSE, 0 );
631 auto button = create_modal_dialog_button( "OK", ok_button );
632 vbox.pack_start( button, FALSE, FALSE, 0 );
633 widget_make_default( button );
634 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
637 auto button = create_modal_dialog_button( "Cancel", cancel_button );
638 vbox.pack_start( button, FALSE, FALSE, 0 );
639 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
644 // Initialize with last used values
647 sprintf( buf, "%f", last_used_texture_layout_scale_x );
650 sprintf( buf, "%f", last_used_texture_layout_scale_y );
653 // Set focus after intializing the values
654 gtk_widget_grab_focus( x );
656 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
657 if ( ret == eIDOK ) {
658 *fx = static_cast<float>( atof( gtk_entry_get_text( x ) ) );
659 *fy = static_cast<float>( atof( gtk_entry_get_text( y ) ) );
661 // Remember last used values
662 last_used_texture_layout_scale_x = *fx;
663 last_used_texture_layout_scale_y = *fy;
671 // =============================================================================
672 // Text Editor dialog
674 // master window widget
675 static ui::Window text_editor{ui::null};
676 static ui::Widget text_widget{ui::null}; // slave, text widget from the gtk editor
677 static GtkTextBuffer* text_buffer_;
679 static gint editor_delete( ui::Widget widget, gpointer data ){
680 /* if ( ui::alert( widget.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
689 static void editor_save( ui::Widget widget, gpointer data ){
690 FILE *f = fopen( (char*)g_object_get_data( G_OBJECT( data ), "filename" ), "w" );
691 //gpointer text = g_object_get_data( G_OBJECT( data ), "text" );
694 ui::alert( ui::Widget::from(data).window(), "Error saving file !" );
698 /* Obtain iters for the start and end of points of the buffer */
701 gtk_text_buffer_get_start_iter (text_buffer_, &start);
702 gtk_text_buffer_get_end_iter (text_buffer_, &end);
704 /* Get the entire buffer text. */
705 char *str = gtk_text_buffer_get_text (text_buffer_, &start, &end, FALSE);
707 //char *str = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
708 fwrite( str, 1, strlen( str ), f );
713 static void editor_close( ui::Widget widget, gpointer data ){
714 /* if ( ui::alert( text_editor.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
721 static void CreateGtkTextEditor(){
722 auto dlg = ui::Window( ui::window_type::TOP );
724 dlg.connect( "delete_event",
725 G_CALLBACK( editor_delete ), 0 );
726 gtk_window_set_default_size( dlg, 400, 300 );
728 auto vbox = ui::VBox( FALSE, 5 );
731 gtk_container_set_border_width( GTK_CONTAINER( vbox ), 5 );
733 auto scr = ui::ScrolledWindow(ui::New);
735 vbox.pack_start( scr, TRUE, TRUE, 0 );
736 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scr ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
737 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( scr ), GTK_SHADOW_IN );
739 auto text = ui::TextView(ui::New);
742 g_object_set_data( G_OBJECT( dlg ), "text", (gpointer) text );
743 gtk_text_view_set_editable( text, TRUE );
745 auto hbox = ui::HBox( FALSE, 5 );
747 vbox.pack_start( hbox, FALSE, TRUE, 0 );
749 auto button = ui::Button( "Close" );
751 hbox.pack_end(button, FALSE, FALSE, 0);
752 button.connect( "clicked",
753 G_CALLBACK( editor_close ), dlg );
754 button.dimensions(60, -1);
756 button = ui::Button( "Save" );
758 hbox.pack_end(button, FALSE, FALSE, 0);
759 button.connect( "clicked",
760 G_CALLBACK( editor_save ), dlg );
761 button.dimensions(60, -1);
767 static void DoGtkTextEditor( const char* filename, guint cursorpos, int length ){
768 if ( !text_editor ) {
769 CreateGtkTextEditor(); // build it the first time we need it
773 FILE *f = fopen( filename, "r" );
776 globalOutputStream() << "Unable to load file " << filename << " in shader editor.\n";
781 fseek( f, 0, SEEK_END );
782 int len = ftell( f );
783 void *buf = malloc( len );
787 fread( buf, 1, len, f );
789 gtk_window_set_title( text_editor, filename );
791 auto text_buffer = gtk_text_view_get_buffer(ui::TextView::from(text_widget));
792 gtk_text_buffer_set_text( text_buffer, (char*)buf, length );
794 old_filename = g_object_get_data( G_OBJECT( text_editor ), "filename" );
795 if ( old_filename ) {
796 free( old_filename );
798 g_object_set_data( G_OBJECT( text_editor ), "filename", strdup( filename ) );
800 // trying to show later
807 // only move the cursor if it's not exceeding the size..
808 // NOTE: this is erroneous, cursorpos is the offset in bytes, not in characters
809 // len is the max size in bytes, not in characters either, but the character count is below that limit..
810 // thinking .. the difference between character count and byte count would be only because of CR/LF?
812 GtkTextIter text_iter;
813 // character offset, not byte offset
814 gtk_text_buffer_get_iter_at_offset( text_buffer, &text_iter, cursorpos );
815 gtk_text_buffer_place_cursor( text_buffer, &text_iter );
816 gtk_text_view_scroll_to_iter( GTK_TEXT_VIEW( text_widget ), &text_iter, 0, TRUE, 0, 0);
820 gtk_widget_queue_draw( text_widget );
823 text_buffer_ = text_buffer;
829 // =============================================================================
830 // Light Intensity dialog
832 EMessageBoxReturn DoLightIntensityDlg( int *intensity ){
834 ui::Entry intensity_entry{ui::null};
835 ModalDialogButton ok_button( dialog, eIDOK );
836 ModalDialogButton cancel_button( dialog, eIDCANCEL );
838 ui::Window window = MainFrame_getWindow().create_modal_dialog_window("Light intensity", dialog, -1, -1 );
840 auto accel_group = ui::AccelGroup(ui::New);
841 window.add_accel_group( accel_group );
844 auto hbox = create_dialog_hbox( 4, 4 );
847 auto vbox = create_dialog_vbox( 4 );
848 hbox.pack_start( vbox, TRUE, TRUE, 0 );
850 auto label = ui::Label( "ESC for default, ENTER to validate" );
852 vbox.pack_start( label, FALSE, FALSE, 0 );
855 auto entry = ui::Entry(ui::New);
857 vbox.pack_start( entry, TRUE, TRUE, 0 );
859 gtk_widget_grab_focus( entry );
861 intensity_entry = entry;
865 auto vbox = create_dialog_vbox( 4 );
866 hbox.pack_start( vbox, FALSE, FALSE, 0 );
869 auto button = create_modal_dialog_button( "OK", ok_button );
870 vbox.pack_start( button, FALSE, FALSE, 0 );
871 widget_make_default( button );
872 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
875 auto button = create_modal_dialog_button( "Cancel", cancel_button );
876 vbox.pack_start( button, FALSE, FALSE, 0 );
877 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
883 sprintf( buf, "%d", *intensity );
884 intensity_entry.text(buf);
886 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
887 if ( ret == eIDOK ) {
888 *intensity = atoi( gtk_entry_get_text( intensity_entry ) );
896 // =============================================================================
897 // Add new shader tag dialog
899 EMessageBoxReturn DoShaderTagDlg( CopiedString* tag, const char* title ){
901 ModalDialogButton ok_button( dialog, eIDOK );
902 ModalDialogButton cancel_button( dialog, eIDCANCEL );
904 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
906 auto accel_group = ui::AccelGroup(ui::New);
907 window.add_accel_group( accel_group );
909 auto textentry = ui::Entry(ui::New);
911 auto hbox = create_dialog_hbox( 4, 4 );
914 auto vbox = create_dialog_vbox( 4 );
915 hbox.pack_start( vbox, TRUE, TRUE, 0 );
917 //GtkLabel* label = GTK_LABEL(gtk_label_new("Enter one ore more tags separated by spaces"));
918 auto label = ui::Label( "ESC to cancel, ENTER to validate" );
920 vbox.pack_start( label, FALSE, FALSE, 0 );
923 auto entry = textentry;
925 vbox.pack_start( entry, TRUE, TRUE, 0 );
927 gtk_widget_grab_focus( entry );
931 auto vbox = create_dialog_vbox( 4 );
932 hbox.pack_start( vbox, FALSE, FALSE, 0 );
935 auto button = create_modal_dialog_button( "OK", ok_button );
936 vbox.pack_start( button, FALSE, FALSE, 0 );
937 widget_make_default( button );
938 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
941 auto button = create_modal_dialog_button( "Cancel", cancel_button );
942 vbox.pack_start( button, FALSE, FALSE, 0 );
943 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
948 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
949 if ( ret == eIDOK ) {
950 *tag = gtk_entry_get_text( textentry );
958 EMessageBoxReturn DoShaderInfoDlg( const char* name, const char* filename, const char* title ){
960 ModalDialogButton ok_button( dialog, eIDOK );
962 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
964 auto accel_group = ui::AccelGroup(ui::New);
965 window.add_accel_group( accel_group );
968 auto hbox = create_dialog_hbox( 4, 4 );
971 auto vbox = create_dialog_vbox( 4 );
972 hbox.pack_start( vbox, FALSE, FALSE, 0 );
974 auto label = ui::Label( "The selected shader" );
976 vbox.pack_start( label, FALSE, FALSE, 0 );
979 auto label = ui::Label( name );
981 vbox.pack_start( label, FALSE, FALSE, 0 );
984 auto label = ui::Label( "is located in file" );
986 vbox.pack_start( label, FALSE, FALSE, 0 );
989 auto label = ui::Label( filename );
991 vbox.pack_start( label, FALSE, FALSE, 0 );
994 auto button = create_modal_dialog_button( "OK", ok_button );
995 vbox.pack_start( button, FALSE, FALSE, 0 );
996 widget_make_default( button );
997 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
1002 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
1012 #include <gdk/gdkwin32.h>
1016 // use the file associations to open files instead of builtin Gtk editor
1017 bool g_TextEditor_useWin32Editor = false;
1019 // custom shader editor
1020 bool g_TextEditor_useCustomEditor = false;
1021 CopiedString g_TextEditor_editorCommand( "" );
1024 void DoTextEditor( const char* filename, int cursorpos, int length ){
1026 if ( g_TextEditor_useWin32Editor ) {
1027 StringOutputStream path( 256 );
1028 StringOutputStream modpath( 256 );
1029 const char* gamename = GlobalRadiant().getGameName();
1030 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1031 const char* enginePath = GlobalRadiant().getEnginePath();
1032 path << enginePath << basegame << '/' << filename;
1033 modpath << enginePath << gamename << '/' << filename;
1034 if ( file_exists( modpath.c_str() ) ){
1035 globalOutputStream() << "opening file '" << modpath.c_str() << "' (line " << cursorpos << " info ignored)\n";
1036 ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", modpath.c_str(), 0, 0, SW_SHOW );
1038 else if ( file_exists( path.c_str() ) ){
1039 globalOutputStream() << "opening file '" << path.c_str() << "' (line " << cursorpos << " info ignored)\n";
1040 ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", path.c_str(), 0, 0, SW_SHOW );
1043 globalOutputStream() << "Failed to open '" << filename << "\n";
1048 StringOutputStream path( 256 );
1049 StringOutputStream modpath( 256 );
1050 const char* gamename = GlobalRadiant().getGameName();
1051 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1052 const char* enginePath = GlobalRadiant().getEnginePath();
1053 path << enginePath << basegame << '/' << filename;
1054 modpath << enginePath << gamename << '/' << filename;
1055 if ( file_exists( modpath.c_str() ) ){
1056 globalOutputStream() << "opening file '" << modpath.c_str() << "' (line " << cursorpos << " info ignored)\n";
1057 DoGtkTextEditor( modpath.c_str(), cursorpos, length );
1059 else if ( file_exists( path.c_str() ) ){
1060 globalOutputStream() << "opening file '" << path.c_str() << "' (line " << cursorpos << " info ignored)\n";
1061 DoGtkTextEditor( path.c_str(), cursorpos, length );
1064 globalOutputStream() << "Failed to open '" << filename << "\n";
1069 // check if a custom editor is set
1070 if ( g_TextEditor_useCustomEditor && !g_TextEditor_editorCommand.empty() ) {
1071 StringOutputStream strEditCommand( 256 );
1072 strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << filename << "\"";
1074 globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n";
1075 // note: linux does not return false if the command failed so it will assume success
1076 if ( Q_Exec( 0, const_cast<char*>( strEditCommand.c_str() ), 0, true, false ) == false ) {
1077 globalOutputStream() << "Failed to execute " << strEditCommand.c_str() << ", using default\n";
1081 // the command (appeared) to run successfully, no need to do anything more
1086 DoGtkTextEditor( filename, cursorpos, length );