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 );
512 auto button = create_dialog_button( "Changes", G_CALLBACK( about_button_changelog ), 0 );
513 vbox2.pack_start( button, FALSE, FALSE, 0 );
516 auto button = create_dialog_button( "Issues", G_CALLBACK( about_button_issues ), 0 );
517 vbox2.pack_start( button, FALSE, FALSE, 0 );
522 auto frame = create_dialog_frame( "OpenGL Properties" );
523 vbox.pack_start( frame, FALSE, FALSE, 0 );
525 auto table = create_dialog_table( 3, 2, 4, 4, 4 );
528 auto label = ui::Label( "Vendor:" );
530 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
531 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
534 auto label = ui::Label( "Version:" );
536 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
537 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
540 auto label = ui::Label( "Renderer:" );
542 table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0});
543 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
546 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VENDOR ) ) );
548 table.attach(label, {1, 2, 0, 1}, {GTK_FILL, 0});
549 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
552 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VERSION ) ) );
554 table.attach(label, {1, 2, 1, 2}, {GTK_FILL, 0});
555 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
558 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_RENDERER ) ) );
560 table.attach(label, {1, 2, 2, 3}, {GTK_FILL, 0});
561 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
565 auto frame = create_dialog_frame( "OpenGL Extensions" );
566 vbox.pack_start( frame, TRUE, TRUE, 0 );
568 auto sc_extensions = create_scrolled_window( ui::Policy::AUTOMATIC, ui::Policy::ALWAYS, 4 );
569 frame.add(sc_extensions);
571 auto text_extensions = ui::TextView(ui::New);
572 gtk_text_view_set_editable( text_extensions, FALSE );
573 sc_extensions.add(text_extensions);
574 text_extensions.text(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)));
575 gtk_text_view_set_wrap_mode( text_extensions, GTK_WRAP_WORD );
576 text_extensions.show();
583 modal_dialog_show( window, dialog );
588 // =============================================================================
589 // TextureLayout dialog
591 // Last used texture scale values
592 static float last_used_texture_layout_scale_x = 4.0;
593 static float last_used_texture_layout_scale_y = 4.0;
595 EMessageBoxReturn DoTextureLayout( float *fx, float *fy ){
597 ModalDialogButton ok_button( dialog, eIDOK );
598 ModalDialogButton cancel_button( dialog, eIDCANCEL );
599 ui::Entry x{ui::null};
600 ui::Entry y{ui::null};
602 auto window = MainFrame_getWindow().create_modal_dialog_window("Patch texture layout", dialog );
604 auto accel = ui::AccelGroup(ui::New);
605 window.add_accel_group( accel );
608 auto hbox = create_dialog_hbox( 4, 4 );
611 auto vbox = create_dialog_vbox( 4 );
612 hbox.pack_start( vbox, TRUE, TRUE, 0 );
614 auto label = ui::Label( "Texture will be fit across the patch based\n"
615 "on the x and y values given. Values of 1x1\n"
616 "will \"fit\" the texture. 2x2 will repeat\n"
619 vbox.pack_start( label, TRUE, TRUE, 0 );
620 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
623 auto table = create_dialog_table( 2, 2, 4, 4 );
625 vbox.pack_start( table, TRUE, TRUE, 0 );
627 auto label = ui::Label( "Texture x:" );
629 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
630 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
633 auto label = ui::Label( "Texture y:" );
635 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
636 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
639 auto entry = ui::Entry(ui::New);
641 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
646 auto entry = ui::Entry(ui::New);
648 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
655 auto vbox = create_dialog_vbox( 4 );
656 hbox.pack_start( vbox, FALSE, FALSE, 0 );
658 auto button = create_modal_dialog_button( "OK", ok_button );
659 vbox.pack_start( button, FALSE, FALSE, 0 );
660 widget_make_default( button );
661 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
664 auto button = create_modal_dialog_button( "Cancel", cancel_button );
665 vbox.pack_start( button, FALSE, FALSE, 0 );
666 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
671 // Initialize with last used values
674 sprintf( buf, "%f", last_used_texture_layout_scale_x );
677 sprintf( buf, "%f", last_used_texture_layout_scale_y );
680 // Set focus after intializing the values
681 gtk_widget_grab_focus( x );
683 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
684 if ( ret == eIDOK ) {
685 *fx = static_cast<float>( atof( gtk_entry_get_text( x ) ) );
686 *fy = static_cast<float>( atof( gtk_entry_get_text( y ) ) );
688 // Remember last used values
689 last_used_texture_layout_scale_x = *fx;
690 last_used_texture_layout_scale_y = *fy;
698 // =============================================================================
699 // Text Editor dialog
701 // master window widget
702 static ui::Window text_editor{ui::null};
703 static ui::Widget text_widget{ui::null}; // slave, text widget from the gtk editor
704 static GtkTextBuffer* text_buffer_;
706 static gint editor_delete( ui::Widget widget, gpointer data ){
707 /* if ( ui::alert( widget.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
716 static void editor_save( ui::Widget widget, gpointer data ){
717 FILE *f = fopen( (char*)g_object_get_data( G_OBJECT( data ), "filename" ), "w" );
718 //gpointer text = g_object_get_data( G_OBJECT( data ), "text" );
721 ui::alert( ui::Widget::from(data).window(), "Error saving file !" );
725 /* Obtain iters for the start and end of points of the buffer */
728 gtk_text_buffer_get_start_iter (text_buffer_, &start);
729 gtk_text_buffer_get_end_iter (text_buffer_, &end);
731 /* Get the entire buffer text. */
732 char *str = gtk_text_buffer_get_text (text_buffer_, &start, &end, FALSE);
734 //char *str = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
735 fwrite( str, 1, strlen( str ), f );
740 static void editor_close( ui::Widget widget, gpointer data ){
741 /* if ( ui::alert( text_editor.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
748 static void CreateGtkTextEditor(){
749 auto dlg = ui::Window( ui::window_type::TOP );
751 dlg.connect( "delete_event",
752 G_CALLBACK( editor_delete ), 0 );
753 gtk_window_set_default_size( dlg, 400, 300 );
755 auto vbox = ui::VBox( FALSE, 5 );
758 gtk_container_set_border_width( GTK_CONTAINER( vbox ), 5 );
760 auto scr = ui::ScrolledWindow(ui::New);
762 vbox.pack_start( scr, TRUE, TRUE, 0 );
763 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scr ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
764 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( scr ), GTK_SHADOW_IN );
766 auto text = ui::TextView(ui::New);
769 g_object_set_data( G_OBJECT( dlg ), "text", (gpointer) text );
770 gtk_text_view_set_editable( text, TRUE );
772 auto hbox = ui::HBox( FALSE, 5 );
774 vbox.pack_start( hbox, FALSE, TRUE, 0 );
776 auto button = ui::Button( "Close" );
778 hbox.pack_end(button, FALSE, FALSE, 0);
779 button.connect( "clicked",
780 G_CALLBACK( editor_close ), dlg );
781 button.dimensions(60, -1);
783 button = ui::Button( "Save" );
785 hbox.pack_end(button, FALSE, FALSE, 0);
786 button.connect( "clicked",
787 G_CALLBACK( editor_save ), dlg );
788 button.dimensions(60, -1);
794 static void DoGtkTextEditor( const char* filename, guint cursorpos, int length ){
795 if ( !text_editor ) {
796 CreateGtkTextEditor(); // build it the first time we need it
800 FILE *f = fopen( filename, "r" );
803 globalOutputStream() << "Unable to load file " << filename << " in shader editor.\n";
808 fseek( f, 0, SEEK_END );
809 int len = ftell( f );
810 void *buf = malloc( len );
814 fread( buf, 1, len, f );
816 gtk_window_set_title( text_editor, filename );
818 auto text_buffer = gtk_text_view_get_buffer(ui::TextView::from(text_widget));
819 gtk_text_buffer_set_text( text_buffer, (char*)buf, length );
821 old_filename = g_object_get_data( G_OBJECT( text_editor ), "filename" );
822 if ( old_filename ) {
823 free( old_filename );
825 g_object_set_data( G_OBJECT( text_editor ), "filename", strdup( filename ) );
827 // trying to show later
829 gtk_window_present( GTK_WINDOW( text_editor ) );
835 // only move the cursor if it's not exceeding the size..
836 // NOTE: this is erroneous, cursorpos is the offset in bytes, not in characters
837 // len is the max size in bytes, not in characters either, but the character count is below that limit..
838 // thinking .. the difference between character count and byte count would be only because of CR/LF?
840 GtkTextIter text_iter;
841 // character offset, not byte offset
842 gtk_text_buffer_get_iter_at_offset( text_buffer, &text_iter, cursorpos );
843 gtk_text_buffer_place_cursor( text_buffer, &text_iter );
844 gtk_text_view_scroll_to_iter( GTK_TEXT_VIEW( text_widget ), &text_iter, 0, TRUE, 0, 0);
848 gtk_widget_queue_draw( text_widget );
851 text_buffer_ = text_buffer;
857 // =============================================================================
858 // Light Intensity dialog
860 EMessageBoxReturn DoLightIntensityDlg( int *intensity ){
862 ui::Entry intensity_entry{ui::null};
863 ModalDialogButton ok_button( dialog, eIDOK );
864 ModalDialogButton cancel_button( dialog, eIDCANCEL );
866 ui::Window window = MainFrame_getWindow().create_modal_dialog_window("Light intensity", dialog, -1, -1 );
868 auto accel_group = ui::AccelGroup(ui::New);
869 window.add_accel_group( accel_group );
872 auto hbox = create_dialog_hbox( 4, 4 );
875 auto vbox = create_dialog_vbox( 4 );
876 hbox.pack_start( vbox, TRUE, TRUE, 0 );
878 auto label = ui::Label( "ESC for default, ENTER to validate" );
880 vbox.pack_start( label, FALSE, FALSE, 0 );
883 auto entry = ui::Entry(ui::New);
885 vbox.pack_start( entry, TRUE, TRUE, 0 );
887 gtk_widget_grab_focus( entry );
889 intensity_entry = entry;
893 auto vbox = create_dialog_vbox( 4 );
894 hbox.pack_start( vbox, FALSE, FALSE, 0 );
897 auto button = create_modal_dialog_button( "OK", ok_button );
898 vbox.pack_start( button, FALSE, FALSE, 0 );
899 widget_make_default( button );
900 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
903 auto button = create_modal_dialog_button( "Cancel", cancel_button );
904 vbox.pack_start( button, FALSE, FALSE, 0 );
905 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
911 sprintf( buf, "%d", *intensity );
912 intensity_entry.text(buf);
914 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
915 if ( ret == eIDOK ) {
916 *intensity = atoi( gtk_entry_get_text( intensity_entry ) );
924 // =============================================================================
925 // Add new shader tag dialog
927 EMessageBoxReturn DoShaderTagDlg( CopiedString* tag, const char* title ){
929 ModalDialogButton ok_button( dialog, eIDOK );
930 ModalDialogButton cancel_button( dialog, eIDCANCEL );
932 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
934 auto accel_group = ui::AccelGroup(ui::New);
935 window.add_accel_group( accel_group );
937 auto textentry = ui::Entry(ui::New);
939 auto hbox = create_dialog_hbox( 4, 4 );
942 auto vbox = create_dialog_vbox( 4 );
943 hbox.pack_start( vbox, TRUE, TRUE, 0 );
945 //GtkLabel* label = GTK_LABEL(gtk_label_new("Enter one ore more tags separated by spaces"));
946 auto label = ui::Label( "ESC to cancel, ENTER to validate" );
948 vbox.pack_start( label, FALSE, FALSE, 0 );
951 auto entry = textentry;
953 vbox.pack_start( entry, TRUE, TRUE, 0 );
955 gtk_widget_grab_focus( entry );
959 auto vbox = create_dialog_vbox( 4 );
960 hbox.pack_start( vbox, FALSE, FALSE, 0 );
963 auto button = create_modal_dialog_button( "OK", ok_button );
964 vbox.pack_start( button, FALSE, FALSE, 0 );
965 widget_make_default( button );
966 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
969 auto button = create_modal_dialog_button( "Cancel", cancel_button );
970 vbox.pack_start( button, FALSE, FALSE, 0 );
971 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
976 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
977 if ( ret == eIDOK ) {
978 *tag = gtk_entry_get_text( textentry );
986 EMessageBoxReturn DoShaderInfoDlg( const char* name, const char* filename, const char* title ){
988 ModalDialogButton ok_button( dialog, eIDOK );
990 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
992 auto accel_group = ui::AccelGroup(ui::New);
993 window.add_accel_group( accel_group );
996 auto hbox = create_dialog_hbox( 4, 4 );
999 auto vbox = create_dialog_vbox( 4 );
1000 hbox.pack_start( vbox, FALSE, FALSE, 0 );
1002 auto label = ui::Label( "The selected shader" );
1004 vbox.pack_start( label, FALSE, FALSE, 0 );
1007 auto label = ui::Label( name );
1009 vbox.pack_start( label, FALSE, FALSE, 0 );
1012 auto label = ui::Label( "is located in file" );
1014 vbox.pack_start( label, FALSE, FALSE, 0 );
1017 auto label = ui::Label( filename );
1019 vbox.pack_start( label, FALSE, FALSE, 0 );
1022 auto button = create_modal_dialog_button( "OK", ok_button );
1023 vbox.pack_start( button, FALSE, FALSE, 0 );
1024 widget_make_default( button );
1025 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
1030 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
1040 #include <gdk/gdkwin32.h>
1044 // use the file associations to open files instead of builtin Gtk editor
1045 bool g_TextEditor_useWin32Editor = false;
1047 // custom shader editor
1048 bool g_TextEditor_useCustomEditor = false;
1049 CopiedString g_TextEditor_editorCommand( "" );
1052 void DoTextEditor( const char* filename, int cursorpos, int length ){
1054 if ( g_TextEditor_useWin32Editor ) {
1055 StringOutputStream path( 256 );
1056 StringOutputStream modpath( 256 );
1057 const char* gamename = GlobalRadiant().getGameName();
1058 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1059 const char* enginePath = GlobalRadiant().getEnginePath();
1060 path << enginePath << basegame << '/' << filename;
1061 modpath << enginePath << gamename << '/' << filename;
1062 if ( file_exists( modpath.c_str() ) ){
1063 globalOutputStream() << "opening file '" << modpath.c_str() << "' (line " << cursorpos << " info ignored)\n";
1064 ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", modpath.c_str(), 0, 0, SW_SHOW );
1066 else if ( file_exists( path.c_str() ) ){
1067 globalOutputStream() << "opening file '" << path.c_str() << "' (line " << cursorpos << " info ignored)\n";
1068 ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", path.c_str(), 0, 0, SW_SHOW );
1071 globalOutputStream() << "Failed to open '" << filename << "\n";
1076 StringOutputStream path( 256 );
1077 StringOutputStream modpath( 256 );
1078 const char* gamename = GlobalRadiant().getGameName();
1079 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1080 const char* enginePath = GlobalRadiant().getEnginePath();
1081 path << enginePath << basegame << '/' << filename;
1082 modpath << enginePath << gamename << '/' << filename;
1083 if ( file_exists( modpath.c_str() ) ){
1084 globalOutputStream() << "opening file '" << modpath.c_str() << "' (line " << cursorpos << " info ignored)\n";
1085 DoGtkTextEditor( modpath.c_str(), cursorpos, length );
1087 else if ( file_exists( path.c_str() ) ){
1088 globalOutputStream() << "opening file '" << path.c_str() << "' (line " << cursorpos << " info ignored)\n";
1089 DoGtkTextEditor( path.c_str(), cursorpos, length );
1092 globalOutputStream() << "Failed to open '" << filename << "\n";
1097 // check if a custom editor is set
1098 if ( g_TextEditor_useCustomEditor && !g_TextEditor_editorCommand.empty() ) {
1099 StringOutputStream strEditCommand( 256 );
1100 strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << filename << "\"";
1102 globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n";
1103 // note: linux does not return false if the command failed so it will assume success
1104 if ( Q_Exec( 0, const_cast<char*>( strEditCommand.c_str() ), 0, true, false ) == false ) {
1105 globalOutputStream() << "Failed to execute " << strEditCommand.c_str() << ", using default\n";
1109 // the command (appeared) to run successfully, no need to do anything more
1114 DoGtkTextEditor( filename, cursorpos, length );