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
834 // only move the cursor if it's not exceeding the size..
835 // NOTE: this is erroneous, cursorpos is the offset in bytes, not in characters
836 // len is the max size in bytes, not in characters either, but the character count is below that limit..
837 // thinking .. the difference between character count and byte count would be only because of CR/LF?
839 GtkTextIter text_iter;
840 // character offset, not byte offset
841 gtk_text_buffer_get_iter_at_offset( text_buffer, &text_iter, cursorpos );
842 gtk_text_buffer_place_cursor( text_buffer, &text_iter );
843 gtk_text_view_scroll_to_iter( GTK_TEXT_VIEW( text_widget ), &text_iter, 0, TRUE, 0, 0);
847 gtk_widget_queue_draw( text_widget );
850 text_buffer_ = text_buffer;
856 // =============================================================================
857 // Light Intensity dialog
859 EMessageBoxReturn DoLightIntensityDlg( int *intensity ){
861 ui::Entry intensity_entry{ui::null};
862 ModalDialogButton ok_button( dialog, eIDOK );
863 ModalDialogButton cancel_button( dialog, eIDCANCEL );
865 ui::Window window = MainFrame_getWindow().create_modal_dialog_window("Light intensity", dialog, -1, -1 );
867 auto accel_group = ui::AccelGroup(ui::New);
868 window.add_accel_group( accel_group );
871 auto hbox = create_dialog_hbox( 4, 4 );
874 auto vbox = create_dialog_vbox( 4 );
875 hbox.pack_start( vbox, TRUE, TRUE, 0 );
877 auto label = ui::Label( "ESC for default, ENTER to validate" );
879 vbox.pack_start( label, FALSE, FALSE, 0 );
882 auto entry = ui::Entry(ui::New);
884 vbox.pack_start( entry, TRUE, TRUE, 0 );
886 gtk_widget_grab_focus( entry );
888 intensity_entry = entry;
892 auto vbox = create_dialog_vbox( 4 );
893 hbox.pack_start( vbox, FALSE, FALSE, 0 );
896 auto button = create_modal_dialog_button( "OK", ok_button );
897 vbox.pack_start( button, FALSE, FALSE, 0 );
898 widget_make_default( button );
899 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
902 auto button = create_modal_dialog_button( "Cancel", cancel_button );
903 vbox.pack_start( button, FALSE, FALSE, 0 );
904 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
910 sprintf( buf, "%d", *intensity );
911 intensity_entry.text(buf);
913 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
914 if ( ret == eIDOK ) {
915 *intensity = atoi( gtk_entry_get_text( intensity_entry ) );
923 // =============================================================================
924 // Add new shader tag dialog
926 EMessageBoxReturn DoShaderTagDlg( CopiedString* tag, const char* title ){
928 ModalDialogButton ok_button( dialog, eIDOK );
929 ModalDialogButton cancel_button( dialog, eIDCANCEL );
931 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
933 auto accel_group = ui::AccelGroup(ui::New);
934 window.add_accel_group( accel_group );
936 auto textentry = ui::Entry(ui::New);
938 auto hbox = create_dialog_hbox( 4, 4 );
941 auto vbox = create_dialog_vbox( 4 );
942 hbox.pack_start( vbox, TRUE, TRUE, 0 );
944 //GtkLabel* label = GTK_LABEL(gtk_label_new("Enter one ore more tags separated by spaces"));
945 auto label = ui::Label( "ESC to cancel, ENTER to validate" );
947 vbox.pack_start( label, FALSE, FALSE, 0 );
950 auto entry = textentry;
952 vbox.pack_start( entry, TRUE, TRUE, 0 );
954 gtk_widget_grab_focus( entry );
958 auto vbox = create_dialog_vbox( 4 );
959 hbox.pack_start( vbox, FALSE, FALSE, 0 );
962 auto button = create_modal_dialog_button( "OK", ok_button );
963 vbox.pack_start( button, FALSE, FALSE, 0 );
964 widget_make_default( button );
965 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
968 auto button = create_modal_dialog_button( "Cancel", cancel_button );
969 vbox.pack_start( button, FALSE, FALSE, 0 );
970 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
975 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
976 if ( ret == eIDOK ) {
977 *tag = gtk_entry_get_text( textentry );
985 EMessageBoxReturn DoShaderInfoDlg( const char* name, const char* filename, const char* title ){
987 ModalDialogButton ok_button( dialog, eIDOK );
989 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
991 auto accel_group = ui::AccelGroup(ui::New);
992 window.add_accel_group( accel_group );
995 auto hbox = create_dialog_hbox( 4, 4 );
998 auto vbox = create_dialog_vbox( 4 );
999 hbox.pack_start( vbox, FALSE, FALSE, 0 );
1001 auto label = ui::Label( "The selected shader" );
1003 vbox.pack_start( label, FALSE, FALSE, 0 );
1006 auto label = ui::Label( name );
1008 vbox.pack_start( label, FALSE, FALSE, 0 );
1011 auto label = ui::Label( "is located in file" );
1013 vbox.pack_start( label, FALSE, FALSE, 0 );
1016 auto label = ui::Label( filename );
1018 vbox.pack_start( label, FALSE, FALSE, 0 );
1021 auto button = create_modal_dialog_button( "OK", ok_button );
1022 vbox.pack_start( button, FALSE, FALSE, 0 );
1023 widget_make_default( button );
1024 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
1029 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
1039 #include <gdk/gdkwin32.h>
1043 // use the file associations to open files instead of builtin Gtk editor
1044 bool g_TextEditor_useWin32Editor = false;
1046 // custom shader editor
1047 bool g_TextEditor_useCustomEditor = false;
1048 CopiedString g_TextEditor_editorCommand( "" );
1051 void DoTextEditor( const char* filename, int cursorpos, int length ){
1053 if ( g_TextEditor_useWin32Editor ) {
1054 StringOutputStream path( 256 );
1055 StringOutputStream modpath( 256 );
1056 const char* gamename = GlobalRadiant().getGameName();
1057 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1058 const char* enginePath = GlobalRadiant().getEnginePath();
1059 path << enginePath << basegame << '/' << filename;
1060 modpath << enginePath << gamename << '/' << filename;
1061 if ( file_exists( modpath.c_str() ) ){
1062 globalOutputStream() << "opening file '" << modpath.c_str() << "' (line " << cursorpos << " info ignored)\n";
1063 ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", modpath.c_str(), 0, 0, SW_SHOW );
1065 else if ( file_exists( path.c_str() ) ){
1066 globalOutputStream() << "opening file '" << path.c_str() << "' (line " << cursorpos << " info ignored)\n";
1067 ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", path.c_str(), 0, 0, SW_SHOW );
1070 globalOutputStream() << "Failed to open '" << filename << "\n";
1075 StringOutputStream path( 256 );
1076 StringOutputStream modpath( 256 );
1077 const char* gamename = GlobalRadiant().getGameName();
1078 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1079 const char* enginePath = GlobalRadiant().getEnginePath();
1080 path << enginePath << basegame << '/' << filename;
1081 modpath << enginePath << gamename << '/' << filename;
1082 if ( file_exists( modpath.c_str() ) ){
1083 globalOutputStream() << "opening file '" << modpath.c_str() << "' (line " << cursorpos << " info ignored)\n";
1084 DoGtkTextEditor( modpath.c_str(), cursorpos, length );
1086 else if ( file_exists( path.c_str() ) ){
1087 globalOutputStream() << "opening file '" << path.c_str() << "' (line " << cursorpos << " info ignored)\n";
1088 DoGtkTextEditor( path.c_str(), cursorpos, length );
1091 globalOutputStream() << "Failed to open '" << filename << "\n";
1096 // check if a custom editor is set
1097 if ( g_TextEditor_useCustomEditor && !g_TextEditor_editorCommand.empty() ) {
1098 StringOutputStream strEditCommand( 256 );
1099 strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << filename << "\"";
1101 globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n";
1102 // note: linux does not return false if the command failed so it will assume success
1103 if ( Q_Exec( 0, const_cast<char*>( strEditCommand.c_str() ), 0, true, false ) == false ) {
1104 globalOutputStream() << "Failed to execute " << strEditCommand.c_str() << ", using default\n";
1108 // the command (appeared) to run successfully, no need to do anything more
1113 DoGtkTextEditor( filename, cursorpos, length );