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>
51 #include <gtk/gtkspinbutton.h>
54 #include "math/aabb.h"
55 #include "container/array.h"
56 #include "generic/static.h"
57 #include "stream/stringstream.h"
59 #include "gtkutil/messagebox.h"
60 #include "gtkutil/image.h"
63 #include "brushmanip.h"
66 #include "texwindow.h"
68 #include "mainframe.h"
69 #include "preferences.h"
73 #include "qerplugin.h"
78 // =============================================================================
79 // Project settings dialog
81 class GameComboConfiguration
84 const char* basegame_dir;
86 const char* known_dir;
90 GameComboConfiguration() :
91 basegame_dir( g_pGameDescription->getRequiredKeyValue( "basegame" ) ),
92 basegame( g_pGameDescription->getRequiredKeyValue( "basegamename" ) ),
93 known_dir( g_pGameDescription->getKeyValue( "knowngame" ) ),
94 known( g_pGameDescription->getKeyValue( "knowngamename" ) ),
95 custom( g_pGameDescription->getRequiredKeyValue( "unknowngamename" ) ){
99 typedef LazyStatic<GameComboConfiguration> LazyStaticGameComboConfiguration;
101 inline GameComboConfiguration& globalGameComboConfiguration(){
102 return LazyStaticGameComboConfiguration::instance();
108 gamecombo_t( int _game, const char* _fs_game, bool _sensitive )
109 : game( _game ), fs_game( _fs_game ), sensitive( _sensitive )
116 gamecombo_t gamecombo_for_dir( const char* dir ){
117 if ( string_equal( dir, globalGameComboConfiguration().basegame_dir ) ) {
118 return gamecombo_t( 0, "", false );
120 else if ( string_equal( dir, globalGameComboConfiguration().known_dir ) ) {
121 return gamecombo_t( 1, dir, false );
125 return gamecombo_t( string_empty( globalGameComboConfiguration().known_dir ) ? 1 : 2, dir, true );
129 gamecombo_t gamecombo_for_gamename( const char* gamename ){
130 if ( ( strlen( gamename ) == 0 ) || !strcmp( gamename, globalGameComboConfiguration().basegame ) ) {
131 return gamecombo_t( 0, "", false );
133 else if ( !strcmp( gamename, globalGameComboConfiguration().known ) ) {
134 return gamecombo_t( 1, globalGameComboConfiguration().known_dir, false );
138 return gamecombo_t( string_empty( globalGameComboConfiguration().known_dir ) ? 1 : 2, "", true );
142 inline void path_copy_clean( char* destination, const char* source ){
143 char* i = destination;
145 while ( *source != '\0' )
147 *i++ = ( *source == '\\' ) ? '/' : *source;
151 if ( i != destination && *( i - 1 ) != '/' ) {
161 ui::ComboBoxText game_select{ui::null};
162 ui::Entry fsgame_entry{ui::null};
165 gboolean OnSelchangeComboWhatgame( ui::Widget widget, GameCombo* combo ){
166 const char *gamename;
169 gtk_combo_box_get_active_iter( combo->game_select, &iter );
170 gtk_tree_model_get( gtk_combo_box_get_model( combo->game_select ), &iter, 0, (gpointer*)&gamename, -1 );
173 gamecombo_t gamecombo = gamecombo_for_gamename( gamename );
175 combo->fsgame_entry.text( gamecombo.fs_game );
176 gtk_widget_set_sensitive( combo->fsgame_entry , gamecombo.sensitive );
184 bool do_mapping_mode;
185 const char* sp_mapping_mode;
186 const char* mp_mapping_mode;
189 do_mapping_mode( !string_empty( g_pGameDescription->getKeyValue( "show_gamemode" ) ) ),
190 sp_mapping_mode( "Single Player mapping mode" ),
191 mp_mapping_mode( "Multiplayer mapping mode" ){
195 typedef LazyStatic<MappingMode> LazyStaticMappingMode;
197 inline MappingMode& globalMappingMode(){
198 return LazyStaticMappingMode::instance();
201 class ProjectSettingsDialog
204 GameCombo game_combo;
205 ui::ComboBox gamemode_combo{ui::null};
208 ui::Window ProjectSettingsDialog_construct( ProjectSettingsDialog& dialog, ModalDialog& modal ){
209 auto window = MainFrame_getWindow().create_dialog_window("Project Settings", G_CALLBACK(dialog_delete_callback ), &modal );
212 auto table1 = create_dialog_table( 1, 2, 4, 4, 4 );
215 auto vbox = create_dialog_vbox( 4 );
216 table1.attach(vbox, {1, 2, 0, 1}, {GTK_FILL, GTK_FILL});
218 auto button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &modal );
219 vbox.pack_start( button, FALSE, FALSE, 0 );
222 auto button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &modal );
223 vbox.pack_start( button, FALSE, FALSE, 0 );
227 auto frame = create_dialog_frame( "Project settings" );
228 table1.attach(frame, {0, 1, 0, 1}, {GTK_EXPAND | GTK_FILL, GTK_FILL});
230 auto table2 = create_dialog_table( ( globalMappingMode().do_mapping_mode ) ? 4 : 3, 2, 4, 4, 4 );
234 auto label = ui::Label( "Select mod" );
236 table2.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
237 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
240 dialog.game_combo.game_select = ui::ComboBoxText(ui::New);
242 gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().basegame );
243 if ( globalGameComboConfiguration().known[0] != '\0' ) {
244 gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().known );
246 gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().custom );
248 dialog.game_combo.game_select.show();
249 table2.attach(dialog.game_combo.game_select, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
251 dialog.game_combo.game_select.connect( "changed", G_CALLBACK( OnSelchangeComboWhatgame ), &dialog.game_combo );
255 auto label = ui::Label( "fs_game" );
257 table2.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
258 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
261 auto entry = ui::Entry(ui::New);
263 table2.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
265 dialog.game_combo.fsgame_entry = entry;
268 if ( globalMappingMode().do_mapping_mode ) {
269 auto label = ui::Label( "Mapping mode" );
271 table2.attach(label, {0, 1, 3, 4}, {GTK_FILL, 0});
272 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
274 auto combo = ui::ComboBoxText(ui::New);
275 gtk_combo_box_text_append_text( combo, globalMappingMode().sp_mapping_mode );
276 gtk_combo_box_text_append_text( combo, globalMappingMode().mp_mapping_mode );
279 table2.attach(combo, {1, 2, 3, 4}, {GTK_EXPAND | GTK_FILL, 0});
281 dialog.gamemode_combo = combo;
287 // initialise the fs_game selection from the project settings into the dialog
288 const char* dir = gamename_get();
289 gamecombo_t gamecombo = gamecombo_for_dir( dir );
291 gtk_combo_box_set_active( dialog.game_combo.game_select, gamecombo.game );
292 dialog.game_combo.fsgame_entry.text( gamecombo.fs_game );
293 gtk_widget_set_sensitive( dialog.game_combo.fsgame_entry , gamecombo.sensitive );
295 if ( globalMappingMode().do_mapping_mode ) {
296 const char *gamemode = gamemode_get();
297 if ( string_empty( gamemode ) || string_equal( gamemode, "sp" ) ) {
298 gtk_combo_box_set_active( dialog.gamemode_combo, 0 );
302 gtk_combo_box_set_active( dialog.gamemode_combo, 1 );
309 void ProjectSettingsDialog_ok( ProjectSettingsDialog& dialog ){
310 const char* dir = gtk_entry_get_text( dialog.game_combo.fsgame_entry );
312 const char* new_gamename = path_equal( dir, globalGameComboConfiguration().basegame_dir )
316 if ( !path_equal( new_gamename, gamename_get() ) ) {
317 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Changing Game Name" );
319 EnginePath_Unrealise();
321 gamename_set( new_gamename );
323 EnginePath_Realise();
326 if ( globalMappingMode().do_mapping_mode ) {
327 // read from gamemode_combo
328 int active = gtk_combo_box_get_active( dialog.gamemode_combo );
329 if ( active == -1 || active == 0 ) {
330 gamemode_set( "sp" );
334 gamemode_set( "mp" );
339 void DoProjectSettings(){
340 //if ( ConfirmModified( "Edit Project Settings" ) ) {
342 ProjectSettingsDialog dialog;
344 ui::Window window = ProjectSettingsDialog_construct( dialog, modal );
346 if ( modal_dialog_show( window, modal ) == eIDOK ) {
347 ProjectSettingsDialog_ok( dialog );
354 // =============================================================================
355 // Arbitrary Sides dialog
357 void DoSides( int type, int axis ){
359 //GtkEntry* sides_entry;
360 GtkWidget* sides_spin;
362 auto window = MainFrame_getWindow().create_dialog_window("Arbitrary sides", G_CALLBACK(dialog_delete_callback ), &dialog );
364 auto accel = ui::AccelGroup(ui::New);
365 window.add_accel_group( accel );
367 auto sides_entry = ui::Entry(ui::New);
369 auto hbox = create_dialog_hbox( 4, 4 );
372 auto label = ui::Label( "Sides:" );
374 hbox.pack_start( label, FALSE, FALSE, 0 );
377 // auto entry = sides_entry;
379 // hbox.pack_start( entry, FALSE, FALSE, 0 );
380 // gtk_widget_grab_focus( entry );
384 EBrushPrefab BrushPrefabType = (EBrushPrefab)type;
385 switch ( BrushPrefabType )
389 adj = GTK_ADJUSTMENT( gtk_adjustment_new( 8, 3, 1022, 1, 10, 0 ) );
392 adj = GTK_ADJUSTMENT( gtk_adjustment_new( 8, 3, 31, 1, 10, 0 ) );
395 adj = GTK_ADJUSTMENT( gtk_adjustment_new( 32, 10, 1000, 1, 10, 0 ) );
398 adj = GTK_ADJUSTMENT( gtk_adjustment_new( 8, 3, 31, 1, 10, 0 ) );
402 GtkWidget* spin = gtk_spin_button_new( adj, 1, 0 );
403 gtk_widget_show( spin );
404 gtk_box_pack_start( GTK_BOX( hbox ), spin, FALSE, FALSE, 0 );
405 gtk_widget_set_size_request( spin, 64, -1 );
406 gtk_spin_button_set_numeric( GTK_SPIN_BUTTON( spin ), TRUE );
411 auto vbox = create_dialog_vbox( 4 );
412 hbox.pack_start( vbox, TRUE, TRUE, 0 );
414 auto button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &dialog );
415 vbox.pack_start( button, FALSE, FALSE, 0 );
416 widget_make_default( button );
417 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
420 auto button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &dialog );
421 vbox.pack_start( button, FALSE, FALSE, 0 );
422 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
427 if ( modal_dialog_show( window, dialog ) == eIDOK ) {
428 // const char *str = gtk_entry_get_text( sides_entry );
430 // Scene_BrushConstructPrefab( GlobalSceneGraph(), (EBrushPrefab)type, atoi( str ), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
431 gtk_spin_button_update ( GTK_SPIN_BUTTON( sides_spin ) );
432 int sides = static_cast<int>( gtk_spin_button_get_value( GTK_SPIN_BUTTON( sides_spin ) ) );
433 Scene_BrushConstructPrefab( GlobalSceneGraph(), (EBrushPrefab)type, sides, TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
439 // =============================================================================
440 // About dialog (no program is complete without one)
442 void about_button_changelog( ui::Widget widget, gpointer data ){
443 StringOutputStream log( 256 );
444 log << "https://gitlab.com/xonotic/netradiant/commits/master";
445 OpenURL( log.c_str() );
448 void about_button_credits( ui::Widget widget, gpointer data ){
449 StringOutputStream cred( 256 );
450 cred << "https://gitlab.com/xonotic/netradiant/graphs/master";
451 OpenURL( cred.c_str() );
454 void about_button_issues( ui::Widget widget, gpointer data ){
455 StringOutputStream cred( 256 );
456 cred << "https://gitlab.com/xonotic/netradiant/issues";
457 OpenURL( cred.c_str() );
460 static void AddParagraph( ui::VBox vbox, const char* text, bool use_markup ){
461 auto label = ui::Label( text );
462 gtk_label_set_use_markup( GTK_LABEL( label ), use_markup );
463 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0 );
464 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
466 vbox.pack_start( label, TRUE, TRUE, 0 );
469 static void AddParagraph( ui::VBox vbox, const char* text ){
470 AddParagraph( vbox, text, false );
475 ModalDialogButton ok_button( dialog, eIDOK );
477 auto window = MainFrame_getWindow().create_modal_dialog_window("About " RADIANT_NAME, dialog );
480 auto vbox = create_dialog_vbox( 4, 4 );
484 auto hbox = create_dialog_hbox( 4 );
485 vbox.pack_start( hbox, FALSE, FALSE, 0 );
488 auto vbox2 = create_dialog_vbox( 4 );
489 hbox.pack_start( vbox2, FALSE, FALSE, 5 );
491 auto frame = create_dialog_frame( 0, ui::Shadow::IN );
492 vbox2.pack_start( frame, FALSE, FALSE, 0 );
494 auto image = new_local_image( "logo.png" );
502 // HACK: that may not be related to font size
503 auto about_vbox = ui::VBox( FALSE, 5 );
505 hbox.pack_start( about_vbox, FALSE, FALSE, 0 );
507 AddParagraph( about_vbox,
508 RADIANT_NAME " " RADIANT_VERSION_STRING " (" __DATE__ ")\n"
510 AddParagraph( about_vbox,
511 "Get news and latest build at "
512 "<a href='https://netradiant.gitlab.io/'>"
513 "netradiant.gitlab.io"
515 "Please report your issues at "
516 "<a href='https://gitlab.com/xonotic/netradiant/issues'>"
517 "gitlab.com/xonotic/netradiant/issues"
519 "The team cannot provide support for custom builds.", true );
520 AddParagraph( about_vbox,
521 RADIANT_NAME " is a community project maintained by "
522 "<a href='https://xonotic.org'>"
525 "and developed with help from "
526 "<a href='https://netradiant.gitlab.io/page/about/'>"
527 "other game projects"
528 "</a> and individuals. ", true );
529 AddParagraph( about_vbox,
530 "This program is free software licensed under the GNU GPL." );
534 auto vbox2 = create_dialog_vbox( 4 );
535 hbox.pack_start( vbox2, TRUE, TRUE, 0 );
537 auto button = create_modal_dialog_button( "OK", ok_button );
538 vbox2.pack_start( button, FALSE, FALSE, 0 );
539 gtk_widget_grab_focus( GTK_WIDGET( button ) );
542 auto button = create_dialog_button( "Credits", G_CALLBACK( about_button_credits ), 0 );
543 vbox2.pack_start( button, FALSE, FALSE, 0 );
544 gtk_widget_set_sensitive( GTK_WIDGET( button ), FALSE);
547 auto button = create_dialog_button( "Changes", G_CALLBACK( about_button_changelog ), 0 );
548 vbox2.pack_start( button, FALSE, FALSE, 0 );
551 auto button = create_dialog_button( "Issues", G_CALLBACK( about_button_issues ), 0 );
552 vbox2.pack_start( button, FALSE, FALSE, 0 );
553 gtk_widget_set_sensitive( GTK_WIDGET( button ), FALSE);
558 auto frame = create_dialog_frame( "OpenGL Properties" );
559 vbox.pack_start( frame, FALSE, FALSE, 0 );
561 auto table = create_dialog_table( 3, 2, 4, 4, 4 );
564 auto label = ui::Label( "Vendor:" );
566 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
567 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
570 auto label = ui::Label( "Version:" );
572 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
573 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
576 auto label = ui::Label( "Renderer:" );
578 table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0});
579 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
582 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VENDOR ) ) );
584 table.attach(label, {1, 2, 0, 1}, {GTK_FILL, 0});
585 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
588 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VERSION ) ) );
590 table.attach(label, {1, 2, 1, 2}, {GTK_FILL, 0});
591 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
594 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_RENDERER ) ) );
596 table.attach(label, {1, 2, 2, 3}, {GTK_FILL, 0});
597 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
601 auto frame = create_dialog_frame( "OpenGL Extensions" );
602 vbox.pack_start( frame, TRUE, TRUE, 0 );
604 auto sc_extensions = create_scrolled_window( ui::Policy::AUTOMATIC, ui::Policy::ALWAYS, 4 );
605 frame.add(sc_extensions);
607 auto text_extensions = ui::TextView(ui::New);
608 gtk_text_view_set_editable( text_extensions, FALSE );
609 sc_extensions.add(text_extensions);
610 text_extensions.text(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)));
611 gtk_text_view_set_wrap_mode( text_extensions, GTK_WRAP_WORD );
612 text_extensions.show();
619 modal_dialog_show( window, dialog );
624 // =============================================================================
625 // TextureLayout dialog
627 // Last used texture scale values
628 static float last_used_texture_layout_scale_x = 4.0;
629 static float last_used_texture_layout_scale_y = 4.0;
631 EMessageBoxReturn DoTextureLayout( float *fx, float *fy ){
633 ModalDialogButton ok_button( dialog, eIDOK );
634 ModalDialogButton cancel_button( dialog, eIDCANCEL );
635 ui::Entry x{ui::null};
636 ui::Entry y{ui::null};
638 auto window = MainFrame_getWindow().create_modal_dialog_window("Patch texture layout", dialog );
640 auto accel = ui::AccelGroup(ui::New);
641 window.add_accel_group( accel );
644 auto hbox = create_dialog_hbox( 4, 4 );
647 auto vbox = create_dialog_vbox( 4 );
648 hbox.pack_start( vbox, TRUE, TRUE, 0 );
650 auto label = ui::Label( "Texture will be fit across the patch based\n"
651 "on the x and y values given. Values of 1x1\n"
652 "will \"fit\" the texture. 2x2 will repeat\n"
655 vbox.pack_start( label, TRUE, TRUE, 0 );
656 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
659 auto table = create_dialog_table( 2, 2, 4, 4 );
661 vbox.pack_start( table, TRUE, TRUE, 0 );
663 auto label = ui::Label( "Texture x:" );
665 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
666 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
669 auto label = ui::Label( "Texture y:" );
671 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
672 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
675 auto entry = ui::Entry(ui::New);
677 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
682 auto entry = ui::Entry(ui::New);
684 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
691 auto vbox = create_dialog_vbox( 4 );
692 hbox.pack_start( vbox, FALSE, FALSE, 0 );
694 auto button = create_modal_dialog_button( "OK", ok_button );
695 vbox.pack_start( button, FALSE, FALSE, 0 );
696 widget_make_default( button );
697 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
700 auto button = create_modal_dialog_button( "Cancel", cancel_button );
701 vbox.pack_start( button, FALSE, FALSE, 0 );
702 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
707 // Initialize with last used values
710 sprintf( buf, "%f", last_used_texture_layout_scale_x );
713 sprintf( buf, "%f", last_used_texture_layout_scale_y );
716 // Set focus after intializing the values
717 gtk_widget_grab_focus( x );
719 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
720 if ( ret == eIDOK ) {
721 *fx = static_cast<float>( atof( gtk_entry_get_text( x ) ) );
722 *fy = static_cast<float>( atof( gtk_entry_get_text( y ) ) );
724 // Remember last used values
725 last_used_texture_layout_scale_x = *fx;
726 last_used_texture_layout_scale_y = *fy;
734 // =============================================================================
735 // Text Editor dialog
737 // master window widget
738 static ui::Window text_editor{ui::null};
739 static ui::Widget text_widget{ui::null}; // slave, text widget from the gtk editor
740 static GtkTextBuffer* text_buffer_;
742 static gint editor_delete( ui::Widget widget, gpointer data ){
743 /* if ( ui::alert( widget.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
752 static void editor_save( ui::Widget widget, gpointer data ){
753 FILE *f = fopen( (char*)g_object_get_data( G_OBJECT( data ), "filename" ), "w" );
754 //gpointer text = g_object_get_data( G_OBJECT( data ), "text" );
757 ui::alert( ui::Widget::from(data).window(), "Error saving file !" );
761 /* Obtain iters for the start and end of points of the buffer */
764 gtk_text_buffer_get_start_iter (text_buffer_, &start);
765 gtk_text_buffer_get_end_iter (text_buffer_, &end);
767 /* Get the entire buffer text. */
768 char *str = gtk_text_buffer_get_text (text_buffer_, &start, &end, FALSE);
770 //char *str = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
771 fwrite( str, 1, strlen( str ), f );
776 static void editor_close( ui::Widget widget, gpointer data ){
777 /* if ( ui::alert( text_editor.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
784 static void CreateGtkTextEditor(){
785 auto dlg = ui::Window( ui::window_type::TOP );
787 dlg.connect( "delete_event",
788 G_CALLBACK( editor_delete ), 0 );
789 gtk_window_set_default_size( dlg, 400, 300 );
791 auto vbox = ui::VBox( FALSE, 5 );
794 gtk_container_set_border_width( GTK_CONTAINER( vbox ), 5 );
796 auto scr = ui::ScrolledWindow(ui::New);
798 vbox.pack_start( scr, TRUE, TRUE, 0 );
799 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scr ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
800 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( scr ), GTK_SHADOW_IN );
802 auto text = ui::TextView(ui::New);
805 g_object_set_data( G_OBJECT( dlg ), "text", (gpointer) text );
806 gtk_text_view_set_editable( text, TRUE );
808 auto hbox = ui::HBox( FALSE, 5 );
810 vbox.pack_start( hbox, FALSE, TRUE, 0 );
812 auto button = ui::Button( "Close" );
814 hbox.pack_end(button, FALSE, FALSE, 0);
815 button.connect( "clicked",
816 G_CALLBACK( editor_close ), dlg );
817 button.dimensions(60, -1);
819 button = ui::Button( "Save" );
821 hbox.pack_end(button, FALSE, FALSE, 0);
822 button.connect( "clicked",
823 G_CALLBACK( editor_save ), dlg );
824 button.dimensions(60, -1);
830 static void DoGtkTextEditor( const char* filename, guint cursorpos, int length ){
831 if ( !text_editor ) {
832 CreateGtkTextEditor(); // build it the first time we need it
836 FILE *f = fopen( filename, "r" );
839 globalOutputStream() << "Unable to load file " << filename << " in shader editor.\n";
844 fseek( f, 0, SEEK_END );
845 int len = ftell( f );
846 void *buf = malloc( len );
850 fread( buf, 1, len, f );
852 gtk_window_set_title( text_editor, filename );
854 auto text_buffer = gtk_text_view_get_buffer(ui::TextView::from(text_widget));
855 gtk_text_buffer_set_text( text_buffer, (char*)buf, length );
857 old_filename = g_object_get_data( G_OBJECT( text_editor ), "filename" );
858 if ( old_filename ) {
859 free( old_filename );
861 g_object_set_data( G_OBJECT( text_editor ), "filename", strdup( filename ) );
863 // trying to show later
865 gtk_window_present( GTK_WINDOW( text_editor ) );
871 // only move the cursor if it's not exceeding the size..
872 // NOTE: this is erroneous, cursorpos is the offset in bytes, not in characters
873 // len is the max size in bytes, not in characters either, but the character count is below that limit..
874 // thinking .. the difference between character count and byte count would be only because of CR/LF?
876 GtkTextIter text_iter;
877 // character offset, not byte offset
878 gtk_text_buffer_get_iter_at_offset( text_buffer, &text_iter, cursorpos );
879 gtk_text_buffer_place_cursor( text_buffer, &text_iter );
880 gtk_text_view_scroll_to_iter( GTK_TEXT_VIEW( text_widget ), &text_iter, 0, TRUE, 0, 0);
884 gtk_widget_queue_draw( text_widget );
887 text_buffer_ = text_buffer;
893 // =============================================================================
894 // Light Intensity dialog
896 EMessageBoxReturn DoLightIntensityDlg( int *intensity ){
898 ui::Entry intensity_entry{ui::null};
899 ModalDialogButton ok_button( dialog, eIDOK );
900 ModalDialogButton cancel_button( dialog, eIDCANCEL );
902 ui::Window window = MainFrame_getWindow().create_modal_dialog_window("Light intensity", dialog, -1, -1 );
904 auto accel_group = ui::AccelGroup(ui::New);
905 window.add_accel_group( accel_group );
908 auto hbox = create_dialog_hbox( 4, 4 );
911 auto vbox = create_dialog_vbox( 4 );
912 hbox.pack_start( vbox, TRUE, TRUE, 0 );
914 auto label = ui::Label( "ESC for default, ENTER to validate" );
916 vbox.pack_start( label, FALSE, FALSE, 0 );
919 auto entry = ui::Entry(ui::New);
921 vbox.pack_start( entry, TRUE, TRUE, 0 );
923 gtk_widget_grab_focus( entry );
925 intensity_entry = entry;
929 auto vbox = create_dialog_vbox( 4 );
930 hbox.pack_start( vbox, FALSE, FALSE, 0 );
933 auto button = create_modal_dialog_button( "OK", ok_button );
934 vbox.pack_start( button, FALSE, FALSE, 0 );
935 widget_make_default( button );
936 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
939 auto button = create_modal_dialog_button( "Cancel", cancel_button );
940 vbox.pack_start( button, FALSE, FALSE, 0 );
941 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
947 sprintf( buf, "%d", *intensity );
948 intensity_entry.text(buf);
950 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
951 if ( ret == eIDOK ) {
952 *intensity = atoi( gtk_entry_get_text( intensity_entry ) );
960 // =============================================================================
961 // Add new shader tag dialog
963 EMessageBoxReturn DoShaderTagDlg( CopiedString* tag, const char* title ){
965 ModalDialogButton ok_button( dialog, eIDOK );
966 ModalDialogButton cancel_button( dialog, eIDCANCEL );
968 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
970 auto accel_group = ui::AccelGroup(ui::New);
971 window.add_accel_group( accel_group );
973 auto textentry = ui::Entry(ui::New);
975 auto hbox = create_dialog_hbox( 4, 4 );
978 auto vbox = create_dialog_vbox( 4 );
979 hbox.pack_start( vbox, TRUE, TRUE, 0 );
981 //GtkLabel* label = GTK_LABEL(gtk_label_new("Enter one ore more tags separated by spaces"));
982 auto label = ui::Label( "ESC to cancel, ENTER to validate" );
984 vbox.pack_start( label, FALSE, FALSE, 0 );
987 auto entry = textentry;
989 vbox.pack_start( entry, TRUE, TRUE, 0 );
991 gtk_widget_grab_focus( entry );
995 auto vbox = create_dialog_vbox( 4 );
996 hbox.pack_start( vbox, FALSE, FALSE, 0 );
999 auto button = create_modal_dialog_button( "OK", ok_button );
1000 vbox.pack_start( button, FALSE, FALSE, 0 );
1001 widget_make_default( button );
1002 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
1005 auto button = create_modal_dialog_button( "Cancel", cancel_button );
1006 vbox.pack_start( button, FALSE, FALSE, 0 );
1007 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
1012 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
1013 if ( ret == eIDOK ) {
1014 *tag = gtk_entry_get_text( textentry );
1022 EMessageBoxReturn DoShaderInfoDlg( const char* name, const char* filename, const char* title ){
1024 ModalDialogButton ok_button( dialog, eIDOK );
1026 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
1028 auto accel_group = ui::AccelGroup(ui::New);
1029 window.add_accel_group( accel_group );
1032 auto hbox = create_dialog_hbox( 4, 4 );
1035 auto vbox = create_dialog_vbox( 4 );
1036 hbox.pack_start( vbox, FALSE, FALSE, 0 );
1038 auto label = ui::Label( "The selected shader" );
1040 vbox.pack_start( label, FALSE, FALSE, 0 );
1043 auto label = ui::Label( name );
1045 vbox.pack_start( label, FALSE, FALSE, 0 );
1048 auto label = ui::Label( "is located in file" );
1050 vbox.pack_start( label, FALSE, FALSE, 0 );
1053 auto label = ui::Label( filename );
1055 vbox.pack_start( label, FALSE, FALSE, 0 );
1058 auto button = create_modal_dialog_button( "OK", ok_button );
1059 vbox.pack_start( button, FALSE, FALSE, 0 );
1060 widget_make_default( button );
1061 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
1066 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
1076 #include <gdk/gdkwin32.h>
1080 // use the file associations to open files instead of builtin Gtk editor
1081 bool g_TextEditor_useWin32Editor = false;
1083 // custom shader editor
1084 bool g_TextEditor_useCustomEditor = false;
1085 CopiedString g_TextEditor_editorCommand( "" );
1088 void DoTextEditor( const char* filename, int cursorpos, int length ){
1090 if ( g_TextEditor_useWin32Editor ) {
1091 StringOutputStream path( 256 );
1092 StringOutputStream modpath( 256 );
1093 const char* gamename = GlobalRadiant().getGameName();
1094 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1095 const char* enginePath = GlobalRadiant().getEnginePath();
1096 path << enginePath << basegame << '/' << filename;
1097 modpath << enginePath << gamename << '/' << filename;
1098 if ( file_exists( modpath.c_str() ) ){
1099 globalOutputStream() << "opening file '" << modpath.c_str() << "' (line " << cursorpos << " info ignored)\n";
1100 ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", modpath.c_str(), 0, 0, SW_SHOW );
1102 else if ( file_exists( path.c_str() ) ){
1103 globalOutputStream() << "opening file '" << path.c_str() << "' (line " << cursorpos << " info ignored)\n";
1104 ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", path.c_str(), 0, 0, SW_SHOW );
1107 globalOutputStream() << "Failed to open '" << filename << "'\nOne sits in .pk3 most likely!\n";
1112 StringOutputStream path( 256 );
1113 StringOutputStream modpath( 256 );
1114 const char* gamename = GlobalRadiant().getGameName();
1115 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1116 const char* enginePath = GlobalRadiant().getEnginePath();
1117 path << enginePath << basegame << '/' << filename;
1118 modpath << enginePath << gamename << '/' << filename;
1119 if ( file_exists( modpath.c_str() ) ){
1120 globalOutputStream() << "opening file '" << modpath.c_str() << "' (line " << cursorpos << " info ignored)\n";
1121 DoGtkTextEditor( modpath.c_str(), cursorpos, length );
1123 else if ( file_exists( path.c_str() ) ){
1124 globalOutputStream() << "opening file '" << path.c_str() << "' (line " << cursorpos << " info ignored)\n";
1125 DoGtkTextEditor( path.c_str(), cursorpos, length );
1128 globalOutputStream() << "Failed to open '" << filename << "'\nOne sits in .pk3 most likely!\n";
1133 // check if a custom editor is set
1134 if ( g_TextEditor_useCustomEditor && !g_TextEditor_editorCommand.empty() ) {
1135 StringOutputStream strEditCommand( 256 );
1136 strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << filename << "\"";
1138 globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n";
1139 // note: linux does not return false if the command failed so it will assume success
1140 if ( Q_Exec( 0, const_cast<char*>( strEditCommand.c_str() ), 0, true, false ) == false ) {
1141 globalOutputStream() << "Failed to execute " << strEditCommand.c_str() << ", using default\n";
1145 // the command (appeared) to run successfully, no need to do anything more
1150 DoGtkTextEditor( filename, cursorpos, length );