2 Copyright (c) 2001, Loki software, inc.
5 Redistribution and use in source and binary forms, with or without modification,
6 are permitted provided that the following conditions are met:
8 Redistributions of source code must retain the above copyright notice, this list
9 of conditions and the following disclaimer.
11 Redistributions in binary form must reproduce the above copyright notice, this
12 list of conditions and the following disclaimer in the documentation and/or
13 other materials provided with the distribution.
15 Neither the name of Loki software nor the names of its contributors may be used
16 to endorse or promote products derived from this software without specific prior
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
23 DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 // Some small dialogs that don't need much
34 // Leonardo Zide (leo@lokigames.com)
38 #include "globaldefs.h"
42 #include "debugging/debugging.h"
47 #include "iscenegraph.h"
48 #include "iselection.h"
50 #include <gdk/gdkkeysyms.h>
51 #include <uilib/uilib.h>
52 #include <gtk/gtkspinbutton.h>
55 #include "math/aabb.h"
56 #include "container/array.h"
57 #include "generic/static.h"
58 #include "stream/stringstream.h"
60 #include "gtkutil/messagebox.h"
61 #include "gtkutil/image.h"
64 #include "brushmanip.h"
67 #include "texwindow.h"
69 #include "mainframe.h"
70 #include "preferences.h"
74 #include "qerplugin.h"
79 // =============================================================================
80 // Project settings dialog
82 class GameComboConfiguration
85 const char* basegame_dir;
87 const char* known_dir;
91 GameComboConfiguration() :
92 basegame_dir( g_pGameDescription->getRequiredKeyValue( "basegame" ) ),
93 basegame( g_pGameDescription->getRequiredKeyValue( "basegamename" ) ),
94 known_dir( g_pGameDescription->getKeyValue( "knowngame" ) ),
95 known( g_pGameDescription->getKeyValue( "knowngamename" ) ),
96 custom( g_pGameDescription->getRequiredKeyValue( "unknowngamename" ) ){
100 typedef LazyStatic<GameComboConfiguration> LazyStaticGameComboConfiguration;
102 inline GameComboConfiguration& globalGameComboConfiguration(){
103 return LazyStaticGameComboConfiguration::instance();
109 gamecombo_t( int _game, const char* _fs_game, bool _sensitive )
110 : game( _game ), fs_game( _fs_game ), sensitive( _sensitive )
117 gamecombo_t gamecombo_for_dir( const char* dir ){
118 if ( string_equal( dir, globalGameComboConfiguration().basegame_dir ) ) {
119 return gamecombo_t( 0, "", false );
121 else if ( string_equal( dir, globalGameComboConfiguration().known_dir ) ) {
122 return gamecombo_t( 1, dir, false );
126 return gamecombo_t( string_empty( globalGameComboConfiguration().known_dir ) ? 1 : 2, dir, true );
130 gamecombo_t gamecombo_for_gamename( const char* gamename ){
131 if ( ( strlen( gamename ) == 0 ) || !strcmp( gamename, globalGameComboConfiguration().basegame ) ) {
132 return gamecombo_t( 0, "", false );
134 else if ( !strcmp( gamename, globalGameComboConfiguration().known ) ) {
135 return gamecombo_t( 1, globalGameComboConfiguration().known_dir, false );
139 return gamecombo_t( string_empty( globalGameComboConfiguration().known_dir ) ? 1 : 2, "", true );
143 inline void path_copy_clean( char* destination, const char* source ){
144 char* i = destination;
146 while ( *source != '\0' )
148 *i++ = ( *source == '\\' ) ? '/' : *source;
152 if ( i != destination && *( i - 1 ) != '/' ) {
162 ui::ComboBoxText game_select{ui::null};
163 ui::Entry fsgame_entry{ui::null};
166 gboolean OnSelchangeComboWhatgame( ui::Widget widget, GameCombo* combo ){
167 const char *gamename;
170 gtk_combo_box_get_active_iter( combo->game_select, &iter );
171 gtk_tree_model_get( gtk_combo_box_get_model( combo->game_select ), &iter, 0, (gpointer*)&gamename, -1 );
174 gamecombo_t gamecombo = gamecombo_for_gamename( gamename );
176 combo->fsgame_entry.text( gamecombo.fs_game );
177 gtk_widget_set_sensitive( combo->fsgame_entry , gamecombo.sensitive );
185 bool do_mapping_mode;
186 const char* sp_mapping_mode;
187 const char* mp_mapping_mode;
190 do_mapping_mode( !string_empty( g_pGameDescription->getKeyValue( "show_gamemode" ) ) ),
191 sp_mapping_mode( "Single Player mapping mode" ),
192 mp_mapping_mode( "Multiplayer mapping mode" ){
196 typedef LazyStatic<MappingMode> LazyStaticMappingMode;
198 inline MappingMode& globalMappingMode(){
199 return LazyStaticMappingMode::instance();
202 class ProjectSettingsDialog
205 GameCombo game_combo;
206 ui::ComboBox gamemode_combo{ui::null};
209 ui::Window ProjectSettingsDialog_construct( ProjectSettingsDialog& dialog, ModalDialog& modal ){
210 auto window = MainFrame_getWindow().create_dialog_window("Project Settings", G_CALLBACK(dialog_delete_callback ), &modal );
213 auto table1 = create_dialog_table( 1, 2, 4, 4, 4 );
216 auto vbox = create_dialog_vbox( 4 );
217 table1.attach(vbox, {1, 2, 0, 1}, {GTK_FILL, GTK_FILL});
219 auto button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &modal );
220 vbox.pack_start( button, FALSE, FALSE, 0 );
223 auto button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &modal );
224 vbox.pack_start( button, FALSE, FALSE, 0 );
228 auto frame = create_dialog_frame( "Project settings" );
229 table1.attach(frame, {0, 1, 0, 1}, {GTK_EXPAND | GTK_FILL, GTK_FILL});
231 auto table2 = create_dialog_table( ( globalMappingMode().do_mapping_mode ) ? 4 : 3, 2, 4, 4, 4 );
235 auto label = ui::Label( "Select mod" );
237 table2.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
238 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
241 dialog.game_combo.game_select = ui::ComboBoxText(ui::New);
243 gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().basegame );
244 if ( globalGameComboConfiguration().known[0] != '\0' ) {
245 gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().known );
247 gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().custom );
249 dialog.game_combo.game_select.show();
250 table2.attach(dialog.game_combo.game_select, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
252 dialog.game_combo.game_select.connect( "changed", G_CALLBACK( OnSelchangeComboWhatgame ), &dialog.game_combo );
256 auto label = ui::Label( "fs_game" );
258 table2.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
259 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
262 auto entry = ui::Entry(ui::New);
264 table2.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
266 dialog.game_combo.fsgame_entry = entry;
269 if ( globalMappingMode().do_mapping_mode ) {
270 auto label = ui::Label( "Mapping mode" );
272 table2.attach(label, {0, 1, 3, 4}, {GTK_FILL, 0});
273 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
275 auto combo = ui::ComboBoxText(ui::New);
276 gtk_combo_box_text_append_text( combo, globalMappingMode().sp_mapping_mode );
277 gtk_combo_box_text_append_text( combo, globalMappingMode().mp_mapping_mode );
280 table2.attach(combo, {1, 2, 3, 4}, {GTK_EXPAND | GTK_FILL, 0});
282 dialog.gamemode_combo = combo;
288 // initialise the fs_game selection from the project settings into the dialog
289 const char* dir = gamename_get();
290 gamecombo_t gamecombo = gamecombo_for_dir( dir );
292 gtk_combo_box_set_active( dialog.game_combo.game_select, gamecombo.game );
293 dialog.game_combo.fsgame_entry.text( gamecombo.fs_game );
294 gtk_widget_set_sensitive( dialog.game_combo.fsgame_entry , gamecombo.sensitive );
296 if ( globalMappingMode().do_mapping_mode ) {
297 const char *gamemode = gamemode_get();
298 if ( string_empty( gamemode ) || string_equal( gamemode, "sp" ) ) {
299 gtk_combo_box_set_active( dialog.gamemode_combo, 0 );
303 gtk_combo_box_set_active( dialog.gamemode_combo, 1 );
310 void ProjectSettingsDialog_ok( ProjectSettingsDialog& dialog ){
311 const char* dir = gtk_entry_get_text( dialog.game_combo.fsgame_entry );
313 const char* new_gamename = path_equal( dir, globalGameComboConfiguration().basegame_dir )
317 if ( !path_equal( new_gamename, gamename_get() ) ) {
318 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Changing Game Name" );
320 EnginePath_Unrealise();
322 gamename_set( new_gamename );
324 EnginePath_Realise();
327 if ( globalMappingMode().do_mapping_mode ) {
328 // read from gamemode_combo
329 int active = gtk_combo_box_get_active( dialog.gamemode_combo );
330 if ( active == -1 || active == 0 ) {
331 gamemode_set( "sp" );
335 gamemode_set( "mp" );
340 void DoProjectSettings(){
341 //if ( ConfirmModified( "Edit Project Settings" ) ) {
343 ProjectSettingsDialog dialog;
345 ui::Window window = ProjectSettingsDialog_construct( dialog, modal );
347 if ( modal_dialog_show( window, modal ) == eIDOK ) {
348 ProjectSettingsDialog_ok( dialog );
355 // =============================================================================
356 // Arbitrary Sides dialog
358 void DoSides( int type, int axis ){
360 //GtkEntry* sides_entry;
361 GtkWidget* sides_spin;
363 auto window = MainFrame_getWindow().create_dialog_window("Arbitrary sides", G_CALLBACK(dialog_delete_callback ), &dialog );
365 auto accel = ui::AccelGroup(ui::New);
366 window.add_accel_group( accel );
368 auto sides_entry = ui::Entry(ui::New);
370 auto hbox = create_dialog_hbox( 4, 4 );
373 auto label = ui::Label( "Sides:" );
375 hbox.pack_start( label, FALSE, FALSE, 0 );
378 // auto entry = sides_entry;
380 // hbox.pack_start( entry, FALSE, FALSE, 0 );
381 // gtk_widget_grab_focus( entry );
385 EBrushPrefab BrushPrefabType = (EBrushPrefab)type;
386 switch ( BrushPrefabType )
390 adj = GTK_ADJUSTMENT( gtk_adjustment_new( 8, 3, 1022, 1, 10, 0 ) );
393 adj = GTK_ADJUSTMENT( gtk_adjustment_new( 8, 3, 31, 1, 10, 0 ) );
396 adj = GTK_ADJUSTMENT( gtk_adjustment_new( 32, 10, 1000, 1, 10, 0 ) );
399 adj = GTK_ADJUSTMENT( gtk_adjustment_new( 8, 3, 31, 1, 10, 0 ) );
403 GtkWidget* spin = gtk_spin_button_new( adj, 1, 0 );
404 gtk_widget_show( spin );
405 gtk_box_pack_start( GTK_BOX( hbox ), spin, FALSE, FALSE, 0 );
406 gtk_widget_set_size_request( spin, 64, -1 );
407 gtk_spin_button_set_numeric( GTK_SPIN_BUTTON( spin ), TRUE );
412 auto vbox = create_dialog_vbox( 4 );
413 hbox.pack_start( vbox, TRUE, TRUE, 0 );
415 auto button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &dialog );
416 vbox.pack_start( button, FALSE, FALSE, 0 );
417 widget_make_default( button );
418 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
421 auto button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &dialog );
422 vbox.pack_start( button, FALSE, FALSE, 0 );
423 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
428 if ( modal_dialog_show( window, dialog ) == eIDOK ) {
429 // const char *str = gtk_entry_get_text( sides_entry );
431 // Scene_BrushConstructPrefab( GlobalSceneGraph(), (EBrushPrefab)type, atoi( str ), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
432 gtk_spin_button_update ( GTK_SPIN_BUTTON( sides_spin ) );
433 int sides = static_cast<int>( gtk_spin_button_get_value( GTK_SPIN_BUTTON( sides_spin ) ) );
434 Scene_BrushConstructPrefab( GlobalSceneGraph(), (EBrushPrefab)type, sides, TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
440 // =============================================================================
441 // About dialog (no program is complete without one)
443 void about_button_changelog( ui::Widget widget, gpointer data ){
444 StringOutputStream log( 256 );
445 log << "https://gitlab.com/xonotic/netradiant/commits/master";
446 OpenURL( log.c_str() );
449 void about_button_credits( ui::Widget widget, gpointer data ){
450 StringOutputStream cred( 256 );
451 cred << "https://gitlab.com/xonotic/netradiant/graphs/master";
452 OpenURL( cred.c_str() );
455 void about_button_issues( ui::Widget widget, gpointer data ){
456 StringOutputStream cred( 256 );
457 cred << "https://gitlab.com/xonotic/netradiant/issues";
458 OpenURL( cred.c_str() );
463 ModalDialogButton ok_button( dialog, eIDOK );
465 auto window = MainFrame_getWindow().create_modal_dialog_window("About NetRadiant", dialog );
468 auto vbox = create_dialog_vbox( 4, 4 );
472 auto hbox = create_dialog_hbox( 4 );
473 vbox.pack_start( hbox, FALSE, TRUE, 0 );
476 auto vbox2 = create_dialog_vbox( 4 );
477 hbox.pack_start( vbox2, TRUE, FALSE, 0 );
479 auto frame = create_dialog_frame( 0, ui::Shadow::IN );
480 vbox2.pack_start( frame, FALSE, FALSE, 0 );
482 auto image = new_local_image( "logo.png" );
490 char const *label_text = "NetRadiant " RADIANT_VERSION "\n"
492 RADIANT_ABOUTMSG "\n\n"
493 "This program is free software\n"
494 "licensed under the GNU GPL.\n\n"
495 "NetRadiant is unsupported, however\n"
496 "you may report your problems at\n"
497 "https://gitlab.com/xonotic/netradiant/issues";
499 auto label = ui::Label( label_text );
502 hbox.pack_start( label, FALSE, FALSE, 0 );
503 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
504 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
508 auto vbox2 = create_dialog_vbox( 4 );
509 hbox.pack_start( vbox2, FALSE, TRUE, 0 );
511 auto button = create_modal_dialog_button( "OK", ok_button );
512 vbox2.pack_start( button, FALSE, FALSE, 0 );
515 auto button = create_dialog_button( "Credits", G_CALLBACK( about_button_credits ), 0 );
516 vbox2.pack_start( button, FALSE, FALSE, 0 );
517 gtk_widget_set_sensitive( GTK_WIDGET( button ), FALSE);
520 auto button = create_dialog_button( "Changes", G_CALLBACK( about_button_changelog ), 0 );
521 vbox2.pack_start( button, FALSE, FALSE, 0 );
524 auto button = create_dialog_button( "Issues", G_CALLBACK( about_button_issues ), 0 );
525 vbox2.pack_start( button, FALSE, FALSE, 0 );
526 gtk_widget_set_sensitive( GTK_WIDGET( button ), FALSE);
531 auto frame = create_dialog_frame( "OpenGL Properties" );
532 vbox.pack_start( frame, FALSE, FALSE, 0 );
534 auto table = create_dialog_table( 3, 2, 4, 4, 4 );
537 auto label = ui::Label( "Vendor:" );
539 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
540 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
543 auto label = ui::Label( "Version:" );
545 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
546 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
549 auto label = ui::Label( "Renderer:" );
551 table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0});
552 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
555 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VENDOR ) ) );
557 table.attach(label, {1, 2, 0, 1}, {GTK_FILL, 0});
558 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
561 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VERSION ) ) );
563 table.attach(label, {1, 2, 1, 2}, {GTK_FILL, 0});
564 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
567 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_RENDERER ) ) );
569 table.attach(label, {1, 2, 2, 3}, {GTK_FILL, 0});
570 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
574 auto frame = create_dialog_frame( "OpenGL Extensions" );
575 vbox.pack_start( frame, TRUE, TRUE, 0 );
577 auto sc_extensions = create_scrolled_window( ui::Policy::AUTOMATIC, ui::Policy::ALWAYS, 4 );
578 frame.add(sc_extensions);
580 auto text_extensions = ui::TextView(ui::New);
581 gtk_text_view_set_editable( text_extensions, FALSE );
582 sc_extensions.add(text_extensions);
583 text_extensions.text(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)));
584 gtk_text_view_set_wrap_mode( text_extensions, GTK_WRAP_WORD );
585 text_extensions.show();
592 modal_dialog_show( window, dialog );
597 // =============================================================================
598 // TextureLayout dialog
600 // Last used texture scale values
601 static float last_used_texture_layout_scale_x = 4.0;
602 static float last_used_texture_layout_scale_y = 4.0;
604 EMessageBoxReturn DoTextureLayout( float *fx, float *fy ){
606 ModalDialogButton ok_button( dialog, eIDOK );
607 ModalDialogButton cancel_button( dialog, eIDCANCEL );
608 ui::Entry x{ui::null};
609 ui::Entry y{ui::null};
611 auto window = MainFrame_getWindow().create_modal_dialog_window("Patch texture layout", dialog );
613 auto accel = ui::AccelGroup(ui::New);
614 window.add_accel_group( accel );
617 auto hbox = create_dialog_hbox( 4, 4 );
620 auto vbox = create_dialog_vbox( 4 );
621 hbox.pack_start( vbox, TRUE, TRUE, 0 );
623 auto label = ui::Label( "Texture will be fit across the patch based\n"
624 "on the x and y values given. Values of 1x1\n"
625 "will \"fit\" the texture. 2x2 will repeat\n"
628 vbox.pack_start( label, TRUE, TRUE, 0 );
629 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
632 auto table = create_dialog_table( 2, 2, 4, 4 );
634 vbox.pack_start( table, TRUE, TRUE, 0 );
636 auto label = ui::Label( "Texture x:" );
638 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
639 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
642 auto label = ui::Label( "Texture y:" );
644 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
645 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
648 auto entry = ui::Entry(ui::New);
650 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
655 auto entry = ui::Entry(ui::New);
657 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
664 auto vbox = create_dialog_vbox( 4 );
665 hbox.pack_start( vbox, FALSE, FALSE, 0 );
667 auto button = create_modal_dialog_button( "OK", ok_button );
668 vbox.pack_start( button, FALSE, FALSE, 0 );
669 widget_make_default( button );
670 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
673 auto button = create_modal_dialog_button( "Cancel", cancel_button );
674 vbox.pack_start( button, FALSE, FALSE, 0 );
675 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
680 // Initialize with last used values
683 sprintf( buf, "%f", last_used_texture_layout_scale_x );
686 sprintf( buf, "%f", last_used_texture_layout_scale_y );
689 // Set focus after intializing the values
690 gtk_widget_grab_focus( x );
692 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
693 if ( ret == eIDOK ) {
694 *fx = static_cast<float>( atof( gtk_entry_get_text( x ) ) );
695 *fy = static_cast<float>( atof( gtk_entry_get_text( y ) ) );
697 // Remember last used values
698 last_used_texture_layout_scale_x = *fx;
699 last_used_texture_layout_scale_y = *fy;
707 // =============================================================================
708 // Text Editor dialog
710 // master window widget
711 static ui::Window text_editor{ui::null};
712 static ui::Widget text_widget{ui::null}; // slave, text widget from the gtk editor
713 static GtkTextBuffer* text_buffer_;
715 static gint editor_delete( ui::Widget widget, gpointer data ){
716 /* if ( ui::alert( widget.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
725 static void editor_save( ui::Widget widget, gpointer data ){
726 FILE *f = fopen( (char*)g_object_get_data( G_OBJECT( data ), "filename" ), "w" );
727 //gpointer text = g_object_get_data( G_OBJECT( data ), "text" );
730 ui::alert( ui::Widget::from(data).window(), "Error saving file !" );
734 /* Obtain iters for the start and end of points of the buffer */
737 gtk_text_buffer_get_start_iter (text_buffer_, &start);
738 gtk_text_buffer_get_end_iter (text_buffer_, &end);
740 /* Get the entire buffer text. */
741 char *str = gtk_text_buffer_get_text (text_buffer_, &start, &end, FALSE);
743 //char *str = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
744 fwrite( str, 1, strlen( str ), f );
749 static void editor_close( ui::Widget widget, gpointer data ){
750 /* if ( ui::alert( text_editor.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
757 static void CreateGtkTextEditor(){
758 auto dlg = ui::Window( ui::window_type::TOP );
760 dlg.connect( "delete_event",
761 G_CALLBACK( editor_delete ), 0 );
762 gtk_window_set_default_size( dlg, 400, 300 );
764 auto vbox = ui::VBox( FALSE, 5 );
767 gtk_container_set_border_width( GTK_CONTAINER( vbox ), 5 );
769 auto scr = ui::ScrolledWindow(ui::New);
771 vbox.pack_start( scr, TRUE, TRUE, 0 );
772 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scr ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
773 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( scr ), GTK_SHADOW_IN );
775 auto text = ui::TextView(ui::New);
778 g_object_set_data( G_OBJECT( dlg ), "text", (gpointer) text );
779 gtk_text_view_set_editable( text, TRUE );
781 auto hbox = ui::HBox( FALSE, 5 );
783 vbox.pack_start( hbox, FALSE, TRUE, 0 );
785 auto button = ui::Button( "Close" );
787 hbox.pack_end(button, FALSE, FALSE, 0);
788 button.connect( "clicked",
789 G_CALLBACK( editor_close ), dlg );
790 button.dimensions(60, -1);
792 button = ui::Button( "Save" );
794 hbox.pack_end(button, FALSE, FALSE, 0);
795 button.connect( "clicked",
796 G_CALLBACK( editor_save ), dlg );
797 button.dimensions(60, -1);
803 static void DoGtkTextEditor( const char* filename, guint cursorpos, int length ){
804 if ( !text_editor ) {
805 CreateGtkTextEditor(); // build it the first time we need it
809 FILE *f = fopen( filename, "r" );
812 globalOutputStream() << "Unable to load file " << filename << " in shader editor.\n";
817 fseek( f, 0, SEEK_END );
818 int len = ftell( f );
819 void *buf = malloc( len );
823 fread( buf, 1, len, f );
825 gtk_window_set_title( text_editor, filename );
827 auto text_buffer = gtk_text_view_get_buffer(ui::TextView::from(text_widget));
828 gtk_text_buffer_set_text( text_buffer, (char*)buf, length );
830 old_filename = g_object_get_data( G_OBJECT( text_editor ), "filename" );
831 if ( old_filename ) {
832 free( old_filename );
834 g_object_set_data( G_OBJECT( text_editor ), "filename", strdup( filename ) );
836 // trying to show later
838 gtk_window_present( GTK_WINDOW( text_editor ) );
844 // only move the cursor if it's not exceeding the size..
845 // NOTE: this is erroneous, cursorpos is the offset in bytes, not in characters
846 // len is the max size in bytes, not in characters either, but the character count is below that limit..
847 // thinking .. the difference between character count and byte count would be only because of CR/LF?
849 GtkTextIter text_iter;
850 // character offset, not byte offset
851 gtk_text_buffer_get_iter_at_offset( text_buffer, &text_iter, cursorpos );
852 gtk_text_buffer_place_cursor( text_buffer, &text_iter );
853 gtk_text_view_scroll_to_iter( GTK_TEXT_VIEW( text_widget ), &text_iter, 0, TRUE, 0, 0);
857 gtk_widget_queue_draw( text_widget );
860 text_buffer_ = text_buffer;
866 // =============================================================================
867 // Light Intensity dialog
869 EMessageBoxReturn DoLightIntensityDlg( int *intensity ){
871 ui::Entry intensity_entry{ui::null};
872 ModalDialogButton ok_button( dialog, eIDOK );
873 ModalDialogButton cancel_button( dialog, eIDCANCEL );
875 ui::Window window = MainFrame_getWindow().create_modal_dialog_window("Light intensity", dialog, -1, -1 );
877 auto accel_group = ui::AccelGroup(ui::New);
878 window.add_accel_group( accel_group );
881 auto hbox = create_dialog_hbox( 4, 4 );
884 auto vbox = create_dialog_vbox( 4 );
885 hbox.pack_start( vbox, TRUE, TRUE, 0 );
887 auto label = ui::Label( "ESC for default, ENTER to validate" );
889 vbox.pack_start( label, FALSE, FALSE, 0 );
892 auto entry = ui::Entry(ui::New);
894 vbox.pack_start( entry, TRUE, TRUE, 0 );
896 gtk_widget_grab_focus( entry );
898 intensity_entry = entry;
902 auto vbox = create_dialog_vbox( 4 );
903 hbox.pack_start( vbox, FALSE, FALSE, 0 );
906 auto button = create_modal_dialog_button( "OK", ok_button );
907 vbox.pack_start( button, FALSE, FALSE, 0 );
908 widget_make_default( button );
909 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
912 auto button = create_modal_dialog_button( "Cancel", cancel_button );
913 vbox.pack_start( button, FALSE, FALSE, 0 );
914 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
920 sprintf( buf, "%d", *intensity );
921 intensity_entry.text(buf);
923 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
924 if ( ret == eIDOK ) {
925 *intensity = atoi( gtk_entry_get_text( intensity_entry ) );
933 // =============================================================================
934 // Add new shader tag dialog
936 EMessageBoxReturn DoShaderTagDlg( CopiedString* tag, const char* title ){
938 ModalDialogButton ok_button( dialog, eIDOK );
939 ModalDialogButton cancel_button( dialog, eIDCANCEL );
941 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
943 auto accel_group = ui::AccelGroup(ui::New);
944 window.add_accel_group( accel_group );
946 auto textentry = ui::Entry(ui::New);
948 auto hbox = create_dialog_hbox( 4, 4 );
951 auto vbox = create_dialog_vbox( 4 );
952 hbox.pack_start( vbox, TRUE, TRUE, 0 );
954 //GtkLabel* label = GTK_LABEL(gtk_label_new("Enter one ore more tags separated by spaces"));
955 auto label = ui::Label( "ESC to cancel, ENTER to validate" );
957 vbox.pack_start( label, FALSE, FALSE, 0 );
960 auto entry = textentry;
962 vbox.pack_start( entry, TRUE, TRUE, 0 );
964 gtk_widget_grab_focus( entry );
968 auto vbox = create_dialog_vbox( 4 );
969 hbox.pack_start( vbox, FALSE, FALSE, 0 );
972 auto button = create_modal_dialog_button( "OK", ok_button );
973 vbox.pack_start( button, FALSE, FALSE, 0 );
974 widget_make_default( button );
975 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
978 auto button = create_modal_dialog_button( "Cancel", cancel_button );
979 vbox.pack_start( button, FALSE, FALSE, 0 );
980 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
985 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
986 if ( ret == eIDOK ) {
987 *tag = gtk_entry_get_text( textentry );
995 EMessageBoxReturn DoShaderInfoDlg( const char* name, const char* filename, const char* title ){
997 ModalDialogButton ok_button( dialog, eIDOK );
999 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
1001 auto accel_group = ui::AccelGroup(ui::New);
1002 window.add_accel_group( accel_group );
1005 auto hbox = create_dialog_hbox( 4, 4 );
1008 auto vbox = create_dialog_vbox( 4 );
1009 hbox.pack_start( vbox, FALSE, FALSE, 0 );
1011 auto label = ui::Label( "The selected shader" );
1013 vbox.pack_start( label, FALSE, FALSE, 0 );
1016 auto label = ui::Label( name );
1018 vbox.pack_start( label, FALSE, FALSE, 0 );
1021 auto label = ui::Label( "is located in file" );
1023 vbox.pack_start( label, FALSE, FALSE, 0 );
1026 auto label = ui::Label( filename );
1028 vbox.pack_start( label, FALSE, FALSE, 0 );
1031 auto button = create_modal_dialog_button( "OK", ok_button );
1032 vbox.pack_start( button, FALSE, FALSE, 0 );
1033 widget_make_default( button );
1034 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
1039 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
1049 #include <gdk/gdkwin32.h>
1053 // use the file associations to open files instead of builtin Gtk editor
1054 bool g_TextEditor_useWin32Editor = false;
1056 // custom shader editor
1057 bool g_TextEditor_useCustomEditor = false;
1058 CopiedString g_TextEditor_editorCommand( "" );
1061 void DoTextEditor( const char* filename, int cursorpos, int length ){
1063 if ( g_TextEditor_useWin32Editor ) {
1064 StringOutputStream path( 256 );
1065 StringOutputStream modpath( 256 );
1066 const char* gamename = GlobalRadiant().getGameName();
1067 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1068 const char* enginePath = GlobalRadiant().getEnginePath();
1069 path << enginePath << basegame << '/' << filename;
1070 modpath << enginePath << gamename << '/' << filename;
1071 if ( file_exists( modpath.c_str() ) ){
1072 globalOutputStream() << "opening file '" << modpath.c_str() << "' (line " << cursorpos << " info ignored)\n";
1073 ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", modpath.c_str(), 0, 0, SW_SHOW );
1075 else if ( file_exists( path.c_str() ) ){
1076 globalOutputStream() << "opening file '" << path.c_str() << "' (line " << cursorpos << " info ignored)\n";
1077 ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", path.c_str(), 0, 0, SW_SHOW );
1080 globalOutputStream() << "Failed to open '" << filename << "'\nOne sits in .pk3 most likely!\n";
1085 StringOutputStream path( 256 );
1086 StringOutputStream modpath( 256 );
1087 const char* gamename = GlobalRadiant().getGameName();
1088 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1089 const char* enginePath = GlobalRadiant().getEnginePath();
1090 path << enginePath << basegame << '/' << filename;
1091 modpath << enginePath << gamename << '/' << filename;
1092 if ( file_exists( modpath.c_str() ) ){
1093 globalOutputStream() << "opening file '" << modpath.c_str() << "' (line " << cursorpos << " info ignored)\n";
1094 DoGtkTextEditor( modpath.c_str(), cursorpos, length );
1096 else if ( file_exists( path.c_str() ) ){
1097 globalOutputStream() << "opening file '" << path.c_str() << "' (line " << cursorpos << " info ignored)\n";
1098 DoGtkTextEditor( path.c_str(), cursorpos, length );
1101 globalOutputStream() << "Failed to open '" << filename << "'\nOne sits in .pk3 most likely!\n";
1106 // check if a custom editor is set
1107 if ( g_TextEditor_useCustomEditor && !g_TextEditor_editorCommand.empty() ) {
1108 StringOutputStream strEditCommand( 256 );
1109 strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << filename << "\"";
1111 globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n";
1112 // note: linux does not return false if the command failed so it will assume success
1113 if ( Q_Exec( 0, const_cast<char*>( strEditCommand.c_str() ), 0, true, false ) == false ) {
1114 globalOutputStream() << "Failed to execute " << strEditCommand.c_str() << ", using default\n";
1118 // the command (appeared) to run successfully, no need to do anything more
1123 DoGtkTextEditor( filename, cursorpos, length );