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 );
483 gtk_widget_set_sensitive( GTK_WIDGET( button ), FALSE);
486 auto button = create_dialog_button( "Changes", G_CALLBACK( about_button_changelog ), 0 );
487 vbox2.pack_start( button, FALSE, FALSE, 0 );
490 auto button = create_dialog_button( "Issues", G_CALLBACK( about_button_issues ), 0 );
491 vbox2.pack_start( button, FALSE, FALSE, 0 );
492 gtk_widget_set_sensitive( GTK_WIDGET( button ), FALSE);
497 auto frame = create_dialog_frame( "OpenGL Properties" );
498 vbox.pack_start( frame, FALSE, FALSE, 0 );
500 auto table = create_dialog_table( 3, 2, 4, 4, 4 );
503 auto label = ui::Label( "Vendor:" );
505 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
506 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
509 auto label = ui::Label( "Version:" );
511 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
512 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
515 auto label = ui::Label( "Renderer:" );
517 table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0});
518 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
521 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VENDOR ) ) );
523 table.attach(label, {1, 2, 0, 1}, {GTK_FILL, 0});
524 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
527 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VERSION ) ) );
529 table.attach(label, {1, 2, 1, 2}, {GTK_FILL, 0});
530 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
533 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_RENDERER ) ) );
535 table.attach(label, {1, 2, 2, 3}, {GTK_FILL, 0});
536 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
540 auto frame = create_dialog_frame( "OpenGL Extensions" );
541 vbox.pack_start( frame, TRUE, TRUE, 0 );
543 auto sc_extensions = create_scrolled_window( ui::Policy::AUTOMATIC, ui::Policy::ALWAYS, 4 );
544 frame.add(sc_extensions);
546 auto text_extensions = ui::TextView(ui::New);
547 gtk_text_view_set_editable( text_extensions, FALSE );
548 sc_extensions.add(text_extensions);
549 text_extensions.text(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)));
550 gtk_text_view_set_wrap_mode( text_extensions, GTK_WRAP_WORD );
551 text_extensions.show();
558 modal_dialog_show( window, dialog );
563 // =============================================================================
564 // TextureLayout dialog
566 // Last used texture scale values
567 static float last_used_texture_layout_scale_x = 4.0;
568 static float last_used_texture_layout_scale_y = 4.0;
570 EMessageBoxReturn DoTextureLayout( float *fx, float *fy ){
572 ModalDialogButton ok_button( dialog, eIDOK );
573 ModalDialogButton cancel_button( dialog, eIDCANCEL );
574 ui::Entry x{ui::null};
575 ui::Entry y{ui::null};
577 auto window = MainFrame_getWindow().create_modal_dialog_window("Patch texture layout", dialog );
579 auto accel = ui::AccelGroup(ui::New);
580 window.add_accel_group( accel );
583 auto hbox = create_dialog_hbox( 4, 4 );
586 auto vbox = create_dialog_vbox( 4 );
587 hbox.pack_start( vbox, TRUE, TRUE, 0 );
589 auto label = ui::Label( "Texture will be fit across the patch based\n"
590 "on the x and y values given. Values of 1x1\n"
591 "will \"fit\" the texture. 2x2 will repeat\n"
594 vbox.pack_start( label, TRUE, TRUE, 0 );
595 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
598 auto table = create_dialog_table( 2, 2, 4, 4 );
600 vbox.pack_start( table, TRUE, TRUE, 0 );
602 auto label = ui::Label( "Texture x:" );
604 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
605 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
608 auto label = ui::Label( "Texture y:" );
610 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
611 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
614 auto entry = ui::Entry(ui::New);
616 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
621 auto entry = ui::Entry(ui::New);
623 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
630 auto vbox = create_dialog_vbox( 4 );
631 hbox.pack_start( vbox, FALSE, FALSE, 0 );
633 auto button = create_modal_dialog_button( "OK", ok_button );
634 vbox.pack_start( button, FALSE, FALSE, 0 );
635 widget_make_default( button );
636 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
639 auto button = create_modal_dialog_button( "Cancel", cancel_button );
640 vbox.pack_start( button, FALSE, FALSE, 0 );
641 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
646 // Initialize with last used values
649 sprintf( buf, "%f", last_used_texture_layout_scale_x );
652 sprintf( buf, "%f", last_used_texture_layout_scale_y );
655 // Set focus after intializing the values
656 gtk_widget_grab_focus( x );
658 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
659 if ( ret == eIDOK ) {
660 *fx = static_cast<float>( atof( gtk_entry_get_text( x ) ) );
661 *fy = static_cast<float>( atof( gtk_entry_get_text( y ) ) );
663 // Remember last used values
664 last_used_texture_layout_scale_x = *fx;
665 last_used_texture_layout_scale_y = *fy;
673 // =============================================================================
674 // Text Editor dialog
676 // master window widget
677 static ui::Window text_editor{ui::null};
678 static ui::Widget text_widget{ui::null}; // slave, text widget from the gtk editor
679 static GtkTextBuffer* text_buffer_;
681 static gint editor_delete( ui::Widget widget, gpointer data ){
682 /* if ( ui::alert( widget.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
691 static void editor_save( ui::Widget widget, gpointer data ){
692 FILE *f = fopen( (char*)g_object_get_data( G_OBJECT( data ), "filename" ), "w" );
693 //gpointer text = g_object_get_data( G_OBJECT( data ), "text" );
696 ui::alert( ui::Widget::from(data).window(), "Error saving file !" );
700 /* Obtain iters for the start and end of points of the buffer */
703 gtk_text_buffer_get_start_iter (text_buffer_, &start);
704 gtk_text_buffer_get_end_iter (text_buffer_, &end);
706 /* Get the entire buffer text. */
707 char *str = gtk_text_buffer_get_text (text_buffer_, &start, &end, FALSE);
709 //char *str = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
710 fwrite( str, 1, strlen( str ), f );
715 static void editor_close( ui::Widget widget, gpointer data ){
716 /* if ( ui::alert( text_editor.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
723 static void CreateGtkTextEditor(){
724 auto dlg = ui::Window( ui::window_type::TOP );
726 dlg.connect( "delete_event",
727 G_CALLBACK( editor_delete ), 0 );
728 gtk_window_set_default_size( dlg, 400, 300 );
730 auto vbox = ui::VBox( FALSE, 5 );
733 gtk_container_set_border_width( GTK_CONTAINER( vbox ), 5 );
735 auto scr = ui::ScrolledWindow(ui::New);
737 vbox.pack_start( scr, TRUE, TRUE, 0 );
738 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scr ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
739 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( scr ), GTK_SHADOW_IN );
741 auto text = ui::TextView(ui::New);
744 g_object_set_data( G_OBJECT( dlg ), "text", (gpointer) text );
745 gtk_text_view_set_editable( text, TRUE );
747 auto hbox = ui::HBox( FALSE, 5 );
749 vbox.pack_start( hbox, FALSE, TRUE, 0 );
751 auto button = ui::Button( "Close" );
753 hbox.pack_end(button, FALSE, FALSE, 0);
754 button.connect( "clicked",
755 G_CALLBACK( editor_close ), dlg );
756 button.dimensions(60, -1);
758 button = ui::Button( "Save" );
760 hbox.pack_end(button, FALSE, FALSE, 0);
761 button.connect( "clicked",
762 G_CALLBACK( editor_save ), dlg );
763 button.dimensions(60, -1);
769 static void DoGtkTextEditor( const char* filename, guint cursorpos, int length ){
770 if ( !text_editor ) {
771 CreateGtkTextEditor(); // build it the first time we need it
775 FILE *f = fopen( filename, "r" );
778 globalOutputStream() << "Unable to load file " << filename << " in shader editor.\n";
783 fseek( f, 0, SEEK_END );
784 int len = ftell( f );
785 void *buf = malloc( len );
789 fread( buf, 1, len, f );
791 gtk_window_set_title( text_editor, filename );
793 auto text_buffer = gtk_text_view_get_buffer(ui::TextView::from(text_widget));
794 gtk_text_buffer_set_text( text_buffer, (char*)buf, length );
796 old_filename = g_object_get_data( G_OBJECT( text_editor ), "filename" );
797 if ( old_filename ) {
798 free( old_filename );
800 g_object_set_data( G_OBJECT( text_editor ), "filename", strdup( filename ) );
802 // trying to show later
804 gtk_window_present( GTK_WINDOW( text_editor ) );
810 // only move the cursor if it's not exceeding the size..
811 // NOTE: this is erroneous, cursorpos is the offset in bytes, not in characters
812 // len is the max size in bytes, not in characters either, but the character count is below that limit..
813 // thinking .. the difference between character count and byte count would be only because of CR/LF?
815 GtkTextIter text_iter;
816 // character offset, not byte offset
817 gtk_text_buffer_get_iter_at_offset( text_buffer, &text_iter, cursorpos );
818 gtk_text_buffer_place_cursor( text_buffer, &text_iter );
819 gtk_text_view_scroll_to_iter( GTK_TEXT_VIEW( text_widget ), &text_iter, 0, TRUE, 0, 0);
823 gtk_widget_queue_draw( text_widget );
826 text_buffer_ = text_buffer;
832 // =============================================================================
833 // Light Intensity dialog
835 EMessageBoxReturn DoLightIntensityDlg( int *intensity ){
837 ui::Entry intensity_entry{ui::null};
838 ModalDialogButton ok_button( dialog, eIDOK );
839 ModalDialogButton cancel_button( dialog, eIDCANCEL );
841 ui::Window window = MainFrame_getWindow().create_modal_dialog_window("Light intensity", dialog, -1, -1 );
843 auto accel_group = ui::AccelGroup(ui::New);
844 window.add_accel_group( accel_group );
847 auto hbox = create_dialog_hbox( 4, 4 );
850 auto vbox = create_dialog_vbox( 4 );
851 hbox.pack_start( vbox, TRUE, TRUE, 0 );
853 auto label = ui::Label( "ESC for default, ENTER to validate" );
855 vbox.pack_start( label, FALSE, FALSE, 0 );
858 auto entry = ui::Entry(ui::New);
860 vbox.pack_start( entry, TRUE, TRUE, 0 );
862 gtk_widget_grab_focus( entry );
864 intensity_entry = entry;
868 auto vbox = create_dialog_vbox( 4 );
869 hbox.pack_start( vbox, FALSE, FALSE, 0 );
872 auto button = create_modal_dialog_button( "OK", ok_button );
873 vbox.pack_start( button, FALSE, FALSE, 0 );
874 widget_make_default( button );
875 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
878 auto button = create_modal_dialog_button( "Cancel", cancel_button );
879 vbox.pack_start( button, FALSE, FALSE, 0 );
880 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
886 sprintf( buf, "%d", *intensity );
887 intensity_entry.text(buf);
889 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
890 if ( ret == eIDOK ) {
891 *intensity = atoi( gtk_entry_get_text( intensity_entry ) );
899 // =============================================================================
900 // Add new shader tag dialog
902 EMessageBoxReturn DoShaderTagDlg( CopiedString* tag, const char* title ){
904 ModalDialogButton ok_button( dialog, eIDOK );
905 ModalDialogButton cancel_button( dialog, eIDCANCEL );
907 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
909 auto accel_group = ui::AccelGroup(ui::New);
910 window.add_accel_group( accel_group );
912 auto textentry = ui::Entry(ui::New);
914 auto hbox = create_dialog_hbox( 4, 4 );
917 auto vbox = create_dialog_vbox( 4 );
918 hbox.pack_start( vbox, TRUE, TRUE, 0 );
920 //GtkLabel* label = GTK_LABEL(gtk_label_new("Enter one ore more tags separated by spaces"));
921 auto label = ui::Label( "ESC to cancel, ENTER to validate" );
923 vbox.pack_start( label, FALSE, FALSE, 0 );
926 auto entry = textentry;
928 vbox.pack_start( entry, TRUE, TRUE, 0 );
930 gtk_widget_grab_focus( entry );
934 auto vbox = create_dialog_vbox( 4 );
935 hbox.pack_start( vbox, FALSE, FALSE, 0 );
938 auto button = create_modal_dialog_button( "OK", ok_button );
939 vbox.pack_start( button, FALSE, FALSE, 0 );
940 widget_make_default( button );
941 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
944 auto button = create_modal_dialog_button( "Cancel", cancel_button );
945 vbox.pack_start( button, FALSE, FALSE, 0 );
946 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
951 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
952 if ( ret == eIDOK ) {
953 *tag = gtk_entry_get_text( textentry );
961 EMessageBoxReturn DoShaderInfoDlg( const char* name, const char* filename, const char* title ){
963 ModalDialogButton ok_button( dialog, eIDOK );
965 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
967 auto accel_group = ui::AccelGroup(ui::New);
968 window.add_accel_group( accel_group );
971 auto hbox = create_dialog_hbox( 4, 4 );
974 auto vbox = create_dialog_vbox( 4 );
975 hbox.pack_start( vbox, FALSE, FALSE, 0 );
977 auto label = ui::Label( "The selected shader" );
979 vbox.pack_start( label, FALSE, FALSE, 0 );
982 auto label = ui::Label( name );
984 vbox.pack_start( label, FALSE, FALSE, 0 );
987 auto label = ui::Label( "is located in file" );
989 vbox.pack_start( label, FALSE, FALSE, 0 );
992 auto label = ui::Label( filename );
994 vbox.pack_start( label, FALSE, FALSE, 0 );
997 auto button = create_modal_dialog_button( "OK", ok_button );
998 vbox.pack_start( button, FALSE, FALSE, 0 );
999 widget_make_default( button );
1000 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
1005 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
1015 #include <gdk/gdkwin32.h>
1019 // use the file associations to open files instead of builtin Gtk editor
1020 bool g_TextEditor_useWin32Editor = false;
1022 // custom shader editor
1023 bool g_TextEditor_useCustomEditor = false;
1024 CopiedString g_TextEditor_editorCommand( "" );
1027 void DoTextEditor( const char* filename, int cursorpos, int length ){
1029 if ( g_TextEditor_useWin32Editor ) {
1030 StringOutputStream path( 256 );
1031 StringOutputStream modpath( 256 );
1032 const char* gamename = GlobalRadiant().getGameName();
1033 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1034 const char* enginePath = GlobalRadiant().getEnginePath();
1035 path << enginePath << basegame << '/' << filename;
1036 modpath << enginePath << gamename << '/' << filename;
1037 if ( file_exists( modpath.c_str() ) ){
1038 globalOutputStream() << "opening file '" << modpath.c_str() << "' (line " << cursorpos << " info ignored)\n";
1039 ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", modpath.c_str(), 0, 0, SW_SHOW );
1041 else if ( file_exists( path.c_str() ) ){
1042 globalOutputStream() << "opening file '" << path.c_str() << "' (line " << cursorpos << " info ignored)\n";
1043 ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", path.c_str(), 0, 0, SW_SHOW );
1046 globalOutputStream() << "Failed to open '" << filename << "'\nOne sits in .pk3 most likely!\n";
1051 StringOutputStream path( 256 );
1052 StringOutputStream modpath( 256 );
1053 const char* gamename = GlobalRadiant().getGameName();
1054 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1055 const char* enginePath = GlobalRadiant().getEnginePath();
1056 path << enginePath << basegame << '/' << filename;
1057 modpath << enginePath << gamename << '/' << filename;
1058 if ( file_exists( modpath.c_str() ) ){
1059 globalOutputStream() << "opening file '" << modpath.c_str() << "' (line " << cursorpos << " info ignored)\n";
1060 DoGtkTextEditor( modpath.c_str(), cursorpos, length );
1062 else if ( file_exists( path.c_str() ) ){
1063 globalOutputStream() << "opening file '" << path.c_str() << "' (line " << cursorpos << " info ignored)\n";
1064 DoGtkTextEditor( path.c_str(), cursorpos, length );
1067 globalOutputStream() << "Failed to open '" << filename << "'\nOne sits in .pk3 most likely!\n";
1072 // check if a custom editor is set
1073 if ( g_TextEditor_useCustomEditor && !g_TextEditor_editorCommand.empty() ) {
1074 StringOutputStream strEditCommand( 256 );
1075 strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << filename << "\"";
1077 globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n";
1078 // note: linux does not return false if the command failed so it will assume success
1079 if ( Q_Exec( 0, const_cast<char*>( strEditCommand.c_str() ), 0, true, false ) == false ) {
1080 globalOutputStream() << "Failed to execute " << strEditCommand.c_str() << ", using default\n";
1084 // the command (appeared) to run successfully, no need to do anything more
1089 DoGtkTextEditor( filename, cursorpos, length );