2 Copyright (c) 2001, Loki software, inc.
5 Redistribution and use in source and binary forms, with or without modification,
6 are permitted provided that the following conditions are met:
8 Redistributions of source code must retain the above copyright notice, this list
9 of conditions and the following disclaimer.
11 Redistributions in binary form must reproduce the above copyright notice, this
12 list of conditions and the following disclaimer in the documentation and/or
13 other materials provided with the distribution.
15 Neither the name of Loki software nor the names of its contributors may be used
16 to endorse or promote products derived from this software without specific prior
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
23 DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 // Some small dialogs that don't need much
34 // Leonardo Zide (leo@lokigames.com)
38 #include "globaldefs.h"
42 #include "debugging/debugging.h"
46 #include "iscenegraph.h"
47 #include "iselection.h"
49 #include <gdk/gdkkeysyms.h>
50 #include <uilib/uilib.h>
53 #include "math/aabb.h"
54 #include "container/array.h"
55 #include "generic/static.h"
56 #include "stream/stringstream.h"
58 #include "gtkutil/messagebox.h"
59 #include "gtkutil/image.h"
62 #include "brushmanip.h"
65 #include "texwindow.h"
67 #include "mainframe.h"
68 #include "preferences.h"
72 #include "qerplugin.h"
77 // =============================================================================
78 // Project settings dialog
80 class GameComboConfiguration
83 const char* basegame_dir;
85 const char* known_dir;
89 GameComboConfiguration() :
90 basegame_dir( g_pGameDescription->getRequiredKeyValue( "basegame" ) ),
91 basegame( g_pGameDescription->getRequiredKeyValue( "basegamename" ) ),
92 known_dir( g_pGameDescription->getKeyValue( "knowngame" ) ),
93 known( g_pGameDescription->getKeyValue( "knowngamename" ) ),
94 custom( g_pGameDescription->getRequiredKeyValue( "unknowngamename" ) ){
98 typedef LazyStatic<GameComboConfiguration> LazyStaticGameComboConfiguration;
100 inline GameComboConfiguration& globalGameComboConfiguration(){
101 return LazyStaticGameComboConfiguration::instance();
107 gamecombo_t( int _game, const char* _fs_game, bool _sensitive )
108 : game( _game ), fs_game( _fs_game ), sensitive( _sensitive )
115 gamecombo_t gamecombo_for_dir( const char* dir ){
116 if ( string_equal( dir, globalGameComboConfiguration().basegame_dir ) ) {
117 return gamecombo_t( 0, "", false );
119 else if ( string_equal( dir, globalGameComboConfiguration().known_dir ) ) {
120 return gamecombo_t( 1, dir, false );
124 return gamecombo_t( string_empty( globalGameComboConfiguration().known_dir ) ? 1 : 2, dir, true );
128 gamecombo_t gamecombo_for_gamename( const char* gamename ){
129 if ( ( strlen( gamename ) == 0 ) || !strcmp( gamename, globalGameComboConfiguration().basegame ) ) {
130 return gamecombo_t( 0, "", false );
132 else if ( !strcmp( gamename, globalGameComboConfiguration().known ) ) {
133 return gamecombo_t( 1, globalGameComboConfiguration().known_dir, false );
137 return gamecombo_t( string_empty( globalGameComboConfiguration().known_dir ) ? 1 : 2, "", true );
141 inline void path_copy_clean( char* destination, const char* source ){
142 char* i = destination;
144 while ( *source != '\0' )
146 *i++ = ( *source == '\\' ) ? '/' : *source;
150 if ( i != destination && *( i - 1 ) != '/' ) {
160 ui::ComboBoxText game_select{ui::null};
161 ui::Entry fsgame_entry{ui::null};
164 gboolean OnSelchangeComboWhatgame( ui::Widget widget, GameCombo* combo ){
165 const char *gamename;
168 gtk_combo_box_get_active_iter( combo->game_select, &iter );
169 gtk_tree_model_get( gtk_combo_box_get_model( combo->game_select ), &iter, 0, (gpointer*)&gamename, -1 );
172 gamecombo_t gamecombo = gamecombo_for_gamename( gamename );
174 combo->fsgame_entry.text( gamecombo.fs_game );
175 gtk_widget_set_sensitive( combo->fsgame_entry , gamecombo.sensitive );
183 bool do_mapping_mode;
184 const char* sp_mapping_mode;
185 const char* mp_mapping_mode;
188 do_mapping_mode( !string_empty( g_pGameDescription->getKeyValue( "show_gamemode" ) ) ),
189 sp_mapping_mode( "Single Player mapping mode" ),
190 mp_mapping_mode( "Multiplayer mapping mode" ){
194 typedef LazyStatic<MappingMode> LazyStaticMappingMode;
196 inline MappingMode& globalMappingMode(){
197 return LazyStaticMappingMode::instance();
200 class ProjectSettingsDialog
203 GameCombo game_combo;
204 ui::ComboBox gamemode_combo{ui::null};
207 ui::Window ProjectSettingsDialog_construct( ProjectSettingsDialog& dialog, ModalDialog& modal ){
208 auto window = MainFrame_getWindow().create_dialog_window("Project Settings", G_CALLBACK(dialog_delete_callback ), &modal );
211 auto table1 = create_dialog_table( 1, 2, 4, 4, 4 );
214 auto vbox = create_dialog_vbox( 4 );
215 table1.attach(vbox, {1, 2, 0, 1}, {GTK_FILL, GTK_FILL});
217 auto button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &modal );
218 vbox.pack_start( button, FALSE, FALSE, 0 );
221 auto button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &modal );
222 vbox.pack_start( button, FALSE, FALSE, 0 );
226 auto frame = create_dialog_frame( "Project settings" );
227 table1.attach(frame, {0, 1, 0, 1}, {GTK_EXPAND | GTK_FILL, GTK_FILL});
229 auto table2 = create_dialog_table( ( globalMappingMode().do_mapping_mode ) ? 4 : 3, 2, 4, 4, 4 );
233 auto label = ui::Label( "Select mod" );
235 table2.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
236 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
239 dialog.game_combo.game_select = ui::ComboBoxText(ui::New);
241 gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().basegame );
242 if ( globalGameComboConfiguration().known[0] != '\0' ) {
243 gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().known );
245 gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().custom );
247 dialog.game_combo.game_select.show();
248 table2.attach(dialog.game_combo.game_select, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
250 dialog.game_combo.game_select.connect( "changed", G_CALLBACK( OnSelchangeComboWhatgame ), &dialog.game_combo );
254 auto label = ui::Label( "fs_game" );
256 table2.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
257 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
260 auto entry = ui::Entry(ui::New);
262 table2.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
264 dialog.game_combo.fsgame_entry = entry;
267 if ( globalMappingMode().do_mapping_mode ) {
268 auto label = ui::Label( "Mapping mode" );
270 table2.attach(label, {0, 1, 3, 4}, {GTK_FILL, 0});
271 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
273 auto combo = ui::ComboBoxText(ui::New);
274 gtk_combo_box_text_append_text( combo, globalMappingMode().sp_mapping_mode );
275 gtk_combo_box_text_append_text( combo, globalMappingMode().mp_mapping_mode );
278 table2.attach(combo, {1, 2, 3, 4}, {GTK_EXPAND | GTK_FILL, 0});
280 dialog.gamemode_combo = combo;
286 // initialise the fs_game selection from the project settings into the dialog
287 const char* dir = gamename_get();
288 gamecombo_t gamecombo = gamecombo_for_dir( dir );
290 gtk_combo_box_set_active( dialog.game_combo.game_select, gamecombo.game );
291 dialog.game_combo.fsgame_entry.text( gamecombo.fs_game );
292 gtk_widget_set_sensitive( dialog.game_combo.fsgame_entry , gamecombo.sensitive );
294 if ( globalMappingMode().do_mapping_mode ) {
295 const char *gamemode = gamemode_get();
296 if ( string_empty( gamemode ) || string_equal( gamemode, "sp" ) ) {
297 gtk_combo_box_set_active( dialog.gamemode_combo, 0 );
301 gtk_combo_box_set_active( dialog.gamemode_combo, 1 );
308 void ProjectSettingsDialog_ok( ProjectSettingsDialog& dialog ){
309 const char* dir = gtk_entry_get_text( dialog.game_combo.fsgame_entry );
311 const char* new_gamename = path_equal( dir, globalGameComboConfiguration().basegame_dir )
315 if ( !path_equal( new_gamename, gamename_get() ) ) {
316 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Changing Game Name" );
318 EnginePath_Unrealise();
320 gamename_set( new_gamename );
322 EnginePath_Realise();
325 if ( globalMappingMode().do_mapping_mode ) {
326 // read from gamemode_combo
327 int active = gtk_combo_box_get_active( dialog.gamemode_combo );
328 if ( active == -1 || active == 0 ) {
329 gamemode_set( "sp" );
333 gamemode_set( "mp" );
338 void DoProjectSettings(){
339 //if ( ConfirmModified( "Edit Project Settings" ) ) {
341 ProjectSettingsDialog dialog;
343 ui::Window window = ProjectSettingsDialog_construct( dialog, modal );
345 if ( modal_dialog_show( window, modal ) == eIDOK ) {
346 ProjectSettingsDialog_ok( dialog );
353 // =============================================================================
354 // Arbitrary Sides dialog
356 void DoSides( int type, int axis ){
359 auto window = MainFrame_getWindow().create_dialog_window("Arbitrary sides", G_CALLBACK(dialog_delete_callback ), &dialog );
361 auto accel = ui::AccelGroup(ui::New);
362 window.add_accel_group( accel );
364 auto sides_entry = ui::Entry(ui::New);
366 auto hbox = create_dialog_hbox( 4, 4 );
369 auto label = ui::Label( "Sides:" );
371 hbox.pack_start( label, FALSE, FALSE, 0 );
374 auto entry = sides_entry;
376 hbox.pack_start( entry, FALSE, FALSE, 0 );
377 gtk_widget_grab_focus( entry );
380 auto vbox = create_dialog_vbox( 4 );
381 hbox.pack_start( vbox, TRUE, TRUE, 0 );
383 auto button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &dialog );
384 vbox.pack_start( button, FALSE, FALSE, 0 );
385 widget_make_default( button );
386 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
389 auto button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &dialog );
390 vbox.pack_start( button, FALSE, FALSE, 0 );
391 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
396 if ( modal_dialog_show( window, dialog ) == eIDOK ) {
397 const char *str = gtk_entry_get_text( sides_entry );
399 Scene_BrushConstructPrefab( GlobalSceneGraph(), (EBrushPrefab)type, atoi( str ), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
405 // =============================================================================
406 // About dialog (no program is complete without one)
408 void about_button_changelog( ui::Widget widget, gpointer data ){
409 StringOutputStream log( 256 );
410 log << "https://gitlab.com/xonotic/netradiant/commits/master";
411 OpenURL( log.c_str() );
414 void about_button_credits( ui::Widget widget, gpointer data ){
415 StringOutputStream cred( 256 );
416 cred << "https://gitlab.com/xonotic/netradiant/graphs/master";
417 OpenURL( cred.c_str() );
420 void about_button_issues( ui::Widget widget, gpointer data ){
421 StringOutputStream cred( 256 );
422 cred << "https://gitlab.com/xonotic/netradiant/issues";
423 OpenURL( cred.c_str() );
426 static void AddParagraph( ui::VBox vbox, const char* text, bool use_markup ){
427 auto label = ui::Label( text );
428 gtk_label_set_use_markup( GTK_LABEL( label ), use_markup );
429 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0 );
430 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
432 vbox.pack_start( label, TRUE, TRUE, 0 );
435 static void AddParagraph( ui::VBox vbox, const char* text ){
436 AddParagraph( vbox, text, false );
441 ModalDialogButton ok_button( dialog, eIDOK );
443 auto window = MainFrame_getWindow().create_modal_dialog_window("About " RADIANT_NAME, dialog );
446 auto vbox = create_dialog_vbox( 4, 4 );
450 auto hbox = create_dialog_hbox( 4 );
451 vbox.pack_start( hbox, FALSE, FALSE, 0 );
454 auto vbox2 = create_dialog_vbox( 4 );
455 hbox.pack_start( vbox2, FALSE, FALSE, 5 );
457 auto frame = create_dialog_frame( 0, ui::Shadow::IN );
458 vbox2.pack_start( frame, FALSE, FALSE, 0 );
460 auto image = new_local_image( "logo.png" );
468 // HACK: that may not be related to font size
469 auto about_vbox = ui::VBox( FALSE, 5 );
471 hbox.pack_start( about_vbox, FALSE, FALSE, 0 );
473 AddParagraph( about_vbox,
474 RADIANT_NAME " " RADIANT_VERSION_STRING " (" __DATE__ ")\n"
476 AddParagraph( about_vbox,
477 "Get news and latest build at "
478 "<a href='https://netradiant.gitlab.io/'>"
479 "netradiant.gitlab.io"
481 "Please report your issues at "
482 "<a href='https://gitlab.com/xonotic/netradiant/issues'>"
483 "gitlab.com/xonotic/netradiant/issues"
485 "The team cannot provide support for custom builds.", true );
486 AddParagraph( about_vbox,
487 RADIANT_NAME " is a community project maintained by "
488 "<a href='https://xonotic.org'>"
491 "and developed with help from "
492 "<a href='https://netradiant.gitlab.io/page/about/'>"
493 "other game projects"
494 "</a> and individuals. ", true );
495 AddParagraph( about_vbox,
496 "This program is free software licensed under the GNU GPL." );
500 auto vbox2 = create_dialog_vbox( 4 );
501 hbox.pack_start( vbox2, TRUE, TRUE, 0 );
503 auto button = create_modal_dialog_button( "OK", ok_button );
504 vbox2.pack_start( button, FALSE, FALSE, 0 );
505 gtk_widget_grab_focus( GTK_WIDGET( button ) );
508 auto button = create_dialog_button( "Credits", G_CALLBACK( about_button_credits ), 0 );
509 vbox2.pack_start( button, FALSE, FALSE, 0 );
510 gtk_widget_set_sensitive( GTK_WIDGET( button ), FALSE);
513 auto button = create_dialog_button( "Changes", G_CALLBACK( about_button_changelog ), 0 );
514 vbox2.pack_start( button, FALSE, FALSE, 0 );
517 auto button = create_dialog_button( "Issues", G_CALLBACK( about_button_issues ), 0 );
518 vbox2.pack_start( button, FALSE, FALSE, 0 );
519 gtk_widget_set_sensitive( GTK_WIDGET( button ), FALSE);
524 auto frame = create_dialog_frame( "OpenGL Properties" );
525 vbox.pack_start( frame, FALSE, FALSE, 0 );
527 auto table = create_dialog_table( 3, 2, 4, 4, 4 );
530 auto label = ui::Label( "Vendor:" );
532 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
533 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
536 auto label = ui::Label( "Version:" );
538 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
539 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
542 auto label = ui::Label( "Renderer:" );
544 table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0});
545 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
548 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VENDOR ) ) );
550 table.attach(label, {1, 2, 0, 1}, {GTK_FILL, 0});
551 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
554 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VERSION ) ) );
556 table.attach(label, {1, 2, 1, 2}, {GTK_FILL, 0});
557 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
560 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_RENDERER ) ) );
562 table.attach(label, {1, 2, 2, 3}, {GTK_FILL, 0});
563 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
567 auto frame = create_dialog_frame( "OpenGL Extensions" );
568 vbox.pack_start( frame, TRUE, TRUE, 0 );
570 auto sc_extensions = create_scrolled_window( ui::Policy::AUTOMATIC, ui::Policy::ALWAYS, 4 );
571 frame.add(sc_extensions);
573 auto text_extensions = ui::TextView(ui::New);
574 gtk_text_view_set_editable( text_extensions, FALSE );
575 sc_extensions.add(text_extensions);
576 text_extensions.text(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)));
577 gtk_text_view_set_wrap_mode( text_extensions, GTK_WRAP_WORD );
578 text_extensions.show();
585 modal_dialog_show( window, dialog );
590 // =============================================================================
591 // TextureLayout dialog
593 // Last used texture scale values
594 static float last_used_texture_layout_scale_x = 4.0;
595 static float last_used_texture_layout_scale_y = 4.0;
597 EMessageBoxReturn DoTextureLayout( float *fx, float *fy ){
599 ModalDialogButton ok_button( dialog, eIDOK );
600 ModalDialogButton cancel_button( dialog, eIDCANCEL );
601 ui::Entry x{ui::null};
602 ui::Entry y{ui::null};
604 auto window = MainFrame_getWindow().create_modal_dialog_window("Patch texture layout", dialog );
606 auto accel = ui::AccelGroup(ui::New);
607 window.add_accel_group( accel );
610 auto hbox = create_dialog_hbox( 4, 4 );
613 auto vbox = create_dialog_vbox( 4 );
614 hbox.pack_start( vbox, TRUE, TRUE, 0 );
616 auto label = ui::Label( "Texture will be fit across the patch based\n"
617 "on the x and y values given. Values of 1x1\n"
618 "will \"fit\" the texture. 2x2 will repeat\n"
621 vbox.pack_start( label, TRUE, TRUE, 0 );
622 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
625 auto table = create_dialog_table( 2, 2, 4, 4 );
627 vbox.pack_start( table, TRUE, TRUE, 0 );
629 auto label = ui::Label( "Texture x:" );
631 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
632 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
635 auto label = ui::Label( "Texture y:" );
637 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
638 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
641 auto entry = ui::Entry(ui::New);
643 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
648 auto entry = ui::Entry(ui::New);
650 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
657 auto vbox = create_dialog_vbox( 4 );
658 hbox.pack_start( vbox, FALSE, FALSE, 0 );
660 auto button = create_modal_dialog_button( "OK", ok_button );
661 vbox.pack_start( button, FALSE, FALSE, 0 );
662 widget_make_default( button );
663 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
666 auto button = create_modal_dialog_button( "Cancel", cancel_button );
667 vbox.pack_start( button, FALSE, FALSE, 0 );
668 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
673 // Initialize with last used values
676 sprintf( buf, "%f", last_used_texture_layout_scale_x );
679 sprintf( buf, "%f", last_used_texture_layout_scale_y );
682 // Set focus after intializing the values
683 gtk_widget_grab_focus( x );
685 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
686 if ( ret == eIDOK ) {
687 *fx = static_cast<float>( atof( gtk_entry_get_text( x ) ) );
688 *fy = static_cast<float>( atof( gtk_entry_get_text( y ) ) );
690 // Remember last used values
691 last_used_texture_layout_scale_x = *fx;
692 last_used_texture_layout_scale_y = *fy;
700 // =============================================================================
701 // Text Editor dialog
703 // master window widget
704 static ui::Window text_editor{ui::null};
705 static ui::Widget text_widget{ui::null}; // slave, text widget from the gtk editor
706 static GtkTextBuffer* text_buffer_;
708 static gint editor_delete( ui::Widget widget, gpointer data ){
709 /* if ( ui::alert( widget.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
718 static void editor_save( ui::Widget widget, gpointer data ){
719 FILE *f = fopen( (char*)g_object_get_data( G_OBJECT( data ), "filename" ), "w" );
720 //gpointer text = g_object_get_data( G_OBJECT( data ), "text" );
723 ui::alert( ui::Widget::from(data).window(), "Error saving file !" );
727 /* Obtain iters for the start and end of points of the buffer */
730 gtk_text_buffer_get_start_iter (text_buffer_, &start);
731 gtk_text_buffer_get_end_iter (text_buffer_, &end);
733 /* Get the entire buffer text. */
734 char *str = gtk_text_buffer_get_text (text_buffer_, &start, &end, FALSE);
736 //char *str = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
737 fwrite( str, 1, strlen( str ), f );
742 static void editor_close( ui::Widget widget, gpointer data ){
743 /* if ( ui::alert( text_editor.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
750 static void CreateGtkTextEditor(){
751 auto dlg = ui::Window( ui::window_type::TOP );
753 dlg.connect( "delete_event",
754 G_CALLBACK( editor_delete ), 0 );
755 gtk_window_set_default_size( dlg, 400, 300 );
757 auto vbox = ui::VBox( FALSE, 5 );
760 gtk_container_set_border_width( GTK_CONTAINER( vbox ), 5 );
762 auto scr = ui::ScrolledWindow(ui::New);
764 vbox.pack_start( scr, TRUE, TRUE, 0 );
765 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scr ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
766 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( scr ), GTK_SHADOW_IN );
768 auto text = ui::TextView(ui::New);
771 g_object_set_data( G_OBJECT( dlg ), "text", (gpointer) text );
772 gtk_text_view_set_editable( text, TRUE );
774 auto hbox = ui::HBox( FALSE, 5 );
776 vbox.pack_start( hbox, FALSE, TRUE, 0 );
778 auto button = ui::Button( "Close" );
780 hbox.pack_end(button, FALSE, FALSE, 0);
781 button.connect( "clicked",
782 G_CALLBACK( editor_close ), dlg );
783 button.dimensions(60, -1);
785 button = ui::Button( "Save" );
787 hbox.pack_end(button, FALSE, FALSE, 0);
788 button.connect( "clicked",
789 G_CALLBACK( editor_save ), dlg );
790 button.dimensions(60, -1);
796 static void DoGtkTextEditor( const char* filename, guint cursorpos, int length ){
797 if ( !text_editor ) {
798 CreateGtkTextEditor(); // build it the first time we need it
802 FILE *f = fopen( filename, "r" );
805 globalOutputStream() << "Unable to load file " << filename << " in shader editor.\n";
810 fseek( f, 0, SEEK_END );
811 int len = ftell( f );
812 void *buf = malloc( len );
816 fread( buf, 1, len, f );
818 gtk_window_set_title( text_editor, filename );
820 auto text_buffer = gtk_text_view_get_buffer(ui::TextView::from(text_widget));
821 gtk_text_buffer_set_text( text_buffer, (char*)buf, length );
823 old_filename = g_object_get_data( G_OBJECT( text_editor ), "filename" );
824 if ( old_filename ) {
825 free( old_filename );
827 g_object_set_data( G_OBJECT( text_editor ), "filename", strdup( filename ) );
829 // trying to show later
831 gtk_window_present( GTK_WINDOW( text_editor ) );
837 // only move the cursor if it's not exceeding the size..
838 // NOTE: this is erroneous, cursorpos is the offset in bytes, not in characters
839 // len is the max size in bytes, not in characters either, but the character count is below that limit..
840 // thinking .. the difference between character count and byte count would be only because of CR/LF?
842 GtkTextIter text_iter;
843 // character offset, not byte offset
844 gtk_text_buffer_get_iter_at_offset( text_buffer, &text_iter, cursorpos );
845 gtk_text_buffer_place_cursor( text_buffer, &text_iter );
846 gtk_text_view_scroll_to_iter( GTK_TEXT_VIEW( text_widget ), &text_iter, 0, TRUE, 0, 0);
850 gtk_widget_queue_draw( text_widget );
853 text_buffer_ = text_buffer;
859 // =============================================================================
860 // Light Intensity dialog
862 EMessageBoxReturn DoLightIntensityDlg( int *intensity ){
864 ui::Entry intensity_entry{ui::null};
865 ModalDialogButton ok_button( dialog, eIDOK );
866 ModalDialogButton cancel_button( dialog, eIDCANCEL );
868 ui::Window window = MainFrame_getWindow().create_modal_dialog_window("Light intensity", dialog, -1, -1 );
870 auto accel_group = ui::AccelGroup(ui::New);
871 window.add_accel_group( accel_group );
874 auto hbox = create_dialog_hbox( 4, 4 );
877 auto vbox = create_dialog_vbox( 4 );
878 hbox.pack_start( vbox, TRUE, TRUE, 0 );
880 auto label = ui::Label( "ESC for default, ENTER to validate" );
882 vbox.pack_start( label, FALSE, FALSE, 0 );
885 auto entry = ui::Entry(ui::New);
887 vbox.pack_start( entry, TRUE, TRUE, 0 );
889 gtk_widget_grab_focus( entry );
891 intensity_entry = entry;
895 auto vbox = create_dialog_vbox( 4 );
896 hbox.pack_start( vbox, FALSE, FALSE, 0 );
899 auto button = create_modal_dialog_button( "OK", ok_button );
900 vbox.pack_start( button, FALSE, FALSE, 0 );
901 widget_make_default( button );
902 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
905 auto button = create_modal_dialog_button( "Cancel", cancel_button );
906 vbox.pack_start( button, FALSE, FALSE, 0 );
907 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
913 sprintf( buf, "%d", *intensity );
914 intensity_entry.text(buf);
916 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
917 if ( ret == eIDOK ) {
918 *intensity = atoi( gtk_entry_get_text( intensity_entry ) );
926 // =============================================================================
927 // Add new shader tag dialog
929 EMessageBoxReturn DoShaderTagDlg( CopiedString* tag, const char* title ){
931 ModalDialogButton ok_button( dialog, eIDOK );
932 ModalDialogButton cancel_button( dialog, eIDCANCEL );
934 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
936 auto accel_group = ui::AccelGroup(ui::New);
937 window.add_accel_group( accel_group );
939 auto textentry = ui::Entry(ui::New);
941 auto hbox = create_dialog_hbox( 4, 4 );
944 auto vbox = create_dialog_vbox( 4 );
945 hbox.pack_start( vbox, TRUE, TRUE, 0 );
947 //GtkLabel* label = GTK_LABEL(gtk_label_new("Enter one ore more tags separated by spaces"));
948 auto label = ui::Label( "ESC to cancel, ENTER to validate" );
950 vbox.pack_start( label, FALSE, FALSE, 0 );
953 auto entry = textentry;
955 vbox.pack_start( entry, TRUE, TRUE, 0 );
957 gtk_widget_grab_focus( entry );
961 auto vbox = create_dialog_vbox( 4 );
962 hbox.pack_start( vbox, FALSE, FALSE, 0 );
965 auto button = create_modal_dialog_button( "OK", ok_button );
966 vbox.pack_start( button, FALSE, FALSE, 0 );
967 widget_make_default( button );
968 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
971 auto button = create_modal_dialog_button( "Cancel", cancel_button );
972 vbox.pack_start( button, FALSE, FALSE, 0 );
973 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
978 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
979 if ( ret == eIDOK ) {
980 *tag = gtk_entry_get_text( textentry );
988 EMessageBoxReturn DoShaderInfoDlg( const char* name, const char* filename, const char* title ){
990 ModalDialogButton ok_button( dialog, eIDOK );
992 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
994 auto accel_group = ui::AccelGroup(ui::New);
995 window.add_accel_group( accel_group );
998 auto hbox = create_dialog_hbox( 4, 4 );
1001 auto vbox = create_dialog_vbox( 4 );
1002 hbox.pack_start( vbox, FALSE, FALSE, 0 );
1004 auto label = ui::Label( "The selected shader" );
1006 vbox.pack_start( label, FALSE, FALSE, 0 );
1009 auto label = ui::Label( name );
1011 vbox.pack_start( label, FALSE, FALSE, 0 );
1014 auto label = ui::Label( "is located in file" );
1016 vbox.pack_start( label, FALSE, FALSE, 0 );
1019 auto label = ui::Label( filename );
1021 vbox.pack_start( label, FALSE, FALSE, 0 );
1024 auto button = create_modal_dialog_button( "OK", ok_button );
1025 vbox.pack_start( button, FALSE, FALSE, 0 );
1026 widget_make_default( button );
1027 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
1032 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
1042 #include <gdk/gdkwin32.h>
1046 // use the file associations to open files instead of builtin Gtk editor
1047 bool g_TextEditor_useWin32Editor = false;
1049 // custom shader editor
1050 bool g_TextEditor_useCustomEditor = false;
1051 CopiedString g_TextEditor_editorCommand( "" );
1054 void DoTextEditor( const char* filename, int cursorpos, int length ){
1056 if ( g_TextEditor_useWin32Editor ) {
1057 StringOutputStream path( 256 );
1058 StringOutputStream modpath( 256 );
1059 const char* gamename = GlobalRadiant().getGameName();
1060 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1061 const char* enginePath = GlobalRadiant().getEnginePath();
1062 path << enginePath << basegame << '/' << filename;
1063 modpath << enginePath << gamename << '/' << filename;
1064 if ( file_exists( modpath.c_str() ) ){
1065 globalOutputStream() << "opening file '" << modpath.c_str() << "' (line " << cursorpos << " info ignored)\n";
1066 ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", modpath.c_str(), 0, 0, SW_SHOW );
1068 else if ( file_exists( path.c_str() ) ){
1069 globalOutputStream() << "opening file '" << path.c_str() << "' (line " << cursorpos << " info ignored)\n";
1070 ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", path.c_str(), 0, 0, SW_SHOW );
1073 globalOutputStream() << "Failed to open '" << filename << "'\nOne sits in .pk3 most likely!\n";
1078 StringOutputStream path( 256 );
1079 StringOutputStream modpath( 256 );
1080 const char* gamename = GlobalRadiant().getGameName();
1081 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1082 const char* enginePath = GlobalRadiant().getEnginePath();
1083 path << enginePath << basegame << '/' << filename;
1084 modpath << enginePath << gamename << '/' << filename;
1085 if ( file_exists( modpath.c_str() ) ){
1086 globalOutputStream() << "opening file '" << modpath.c_str() << "' (line " << cursorpos << " info ignored)\n";
1087 DoGtkTextEditor( modpath.c_str(), cursorpos, length );
1089 else if ( file_exists( path.c_str() ) ){
1090 globalOutputStream() << "opening file '" << path.c_str() << "' (line " << cursorpos << " info ignored)\n";
1091 DoGtkTextEditor( path.c_str(), cursorpos, length );
1094 globalOutputStream() << "Failed to open '" << filename << "'\nOne sits in .pk3 most likely!\n";
1099 // check if a custom editor is set
1100 if ( g_TextEditor_useCustomEditor && !g_TextEditor_editorCommand.empty() ) {
1101 StringOutputStream strEditCommand( 256 );
1102 strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << filename << "\"";
1104 globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n";
1105 // note: linux does not return false if the command failed so it will assume success
1106 if ( Q_Exec( 0, const_cast<char*>( strEditCommand.c_str() ), 0, true, false ) == false ) {
1107 globalOutputStream() << "Failed to execute " << strEditCommand.c_str() << ", using default\n";
1111 // the command (appeared) to run successfully, no need to do anything more
1116 DoGtkTextEditor( filename, cursorpos, length );