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)
41 #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"
74 // =============================================================================
75 // Project settings dialog
77 class GameComboConfiguration
80 const char* basegame_dir;
82 const char* known_dir;
86 GameComboConfiguration() :
87 basegame_dir( g_pGameDescription->getRequiredKeyValue( "basegame" ) ),
88 basegame( g_pGameDescription->getRequiredKeyValue( "basegamename" ) ),
89 known_dir( g_pGameDescription->getKeyValue( "knowngame" ) ),
90 known( g_pGameDescription->getKeyValue( "knowngamename" ) ),
91 custom( g_pGameDescription->getRequiredKeyValue( "unknowngamename" ) ){
95 typedef LazyStatic<GameComboConfiguration> LazyStaticGameComboConfiguration;
97 inline GameComboConfiguration& globalGameComboConfiguration(){
98 return LazyStaticGameComboConfiguration::instance();
104 gamecombo_t( int _game, const char* _fs_game, bool _sensitive )
105 : game( _game ), fs_game( _fs_game ), sensitive( _sensitive )
112 gamecombo_t gamecombo_for_dir( const char* dir ){
113 if ( string_equal( dir, globalGameComboConfiguration().basegame_dir ) ) {
114 return gamecombo_t( 0, "", false );
116 else if ( string_equal( dir, globalGameComboConfiguration().known_dir ) ) {
117 return gamecombo_t( 1, dir, false );
121 return gamecombo_t( string_empty( globalGameComboConfiguration().known_dir ) ? 1 : 2, dir, true );
125 gamecombo_t gamecombo_for_gamename( const char* gamename ){
126 if ( ( strlen( gamename ) == 0 ) || !strcmp( gamename, globalGameComboConfiguration().basegame ) ) {
127 return gamecombo_t( 0, "", false );
129 else if ( !strcmp( gamename, globalGameComboConfiguration().known ) ) {
130 return gamecombo_t( 1, globalGameComboConfiguration().known_dir, false );
134 return gamecombo_t( string_empty( globalGameComboConfiguration().known_dir ) ? 1 : 2, "", true );
138 inline void path_copy_clean( char* destination, const char* source ){
139 char* i = destination;
141 while ( *source != '\0' )
143 *i++ = ( *source == '\\' ) ? '/' : *source;
147 if ( i != destination && *( i - 1 ) != '/' ) {
157 ui::ComboBoxText game_select{ui::null};
158 ui::Entry fsgame_entry{ui::null};
161 gboolean OnSelchangeComboWhatgame( ui::Widget widget, GameCombo* combo ){
162 const char *gamename;
165 gtk_combo_box_get_active_iter( combo->game_select, &iter );
166 gtk_tree_model_get( gtk_combo_box_get_model( combo->game_select ), &iter, 0, (gpointer*)&gamename, -1 );
169 gamecombo_t gamecombo = gamecombo_for_gamename( gamename );
171 combo->fsgame_entry.text( gamecombo.fs_game );
172 gtk_widget_set_sensitive( GTK_WIDGET( combo->fsgame_entry ), gamecombo.sensitive );
180 bool do_mapping_mode;
181 const char* sp_mapping_mode;
182 const char* mp_mapping_mode;
185 do_mapping_mode( !string_empty( g_pGameDescription->getKeyValue( "show_gamemode" ) ) ),
186 sp_mapping_mode( "Single Player mapping mode" ),
187 mp_mapping_mode( "Multiplayer mapping mode" ){
191 typedef LazyStatic<MappingMode> LazyStaticMappingMode;
193 inline MappingMode& globalMappingMode(){
194 return LazyStaticMappingMode::instance();
197 class ProjectSettingsDialog
200 GameCombo game_combo;
201 GtkComboBox* gamemode_combo;
204 ui::Window ProjectSettingsDialog_construct( ProjectSettingsDialog& dialog, ModalDialog& modal ){
205 auto window = MainFrame_getWindow().create_dialog_window("Project Settings", G_CALLBACK(dialog_delete_callback ), &modal );
208 auto table1 = create_dialog_table( 1, 2, 4, 4, 4 );
211 GtkVBox* vbox = create_dialog_vbox( 4 );
212 gtk_table_attach( table1, GTK_WIDGET( vbox ), 1, 2, 0, 1,
213 (GtkAttachOptions) ( GTK_FILL ),
214 (GtkAttachOptions) ( GTK_FILL ), 0, 0 );
216 GtkButton* button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &modal );
217 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
220 GtkButton* button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &modal );
221 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
225 auto frame = create_dialog_frame( "Project settings" );
226 gtk_table_attach( table1, GTK_WIDGET( frame ), 0, 1, 0, 1,
227 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
228 (GtkAttachOptions) ( GTK_FILL ), 0, 0 );
230 auto table2 = create_dialog_table( ( globalMappingMode().do_mapping_mode ) ? 4 : 3, 2, 4, 4, 4 );
234 auto label = ui::Label( "Select mod" );
236 gtk_table_attach( table2, GTK_WIDGET( label ), 0, 1, 0, 1,
237 (GtkAttachOptions) ( GTK_FILL ),
238 (GtkAttachOptions) ( 0 ), 0, 0 );
239 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
242 dialog.game_combo.game_select = ui::ComboBoxText(ui::New);
244 gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().basegame );
245 if ( globalGameComboConfiguration().known[0] != '\0' ) {
246 gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().known );
248 gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().custom );
250 dialog.game_combo.game_select.show();
251 gtk_table_attach( table2, GTK_WIDGET( dialog.game_combo.game_select ), 1, 2, 0, 1,
252 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
253 (GtkAttachOptions) ( 0 ), 0, 0 );
255 dialog.game_combo.game_select.connect( "changed", G_CALLBACK( OnSelchangeComboWhatgame ), &dialog.game_combo );
259 auto label = ui::Label( "fs_game" );
261 gtk_table_attach( table2, GTK_WIDGET( label ), 0, 1, 1, 2,
262 (GtkAttachOptions) ( GTK_FILL ),
263 (GtkAttachOptions) ( 0 ), 0, 0 );
264 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
267 auto entry = ui::Entry(ui::New);
269 gtk_table_attach( table2, GTK_WIDGET( entry ), 1, 2, 1, 2,
270 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
271 (GtkAttachOptions) ( 0 ), 0, 0 );
273 dialog.game_combo.fsgame_entry = entry;
276 if ( globalMappingMode().do_mapping_mode ) {
277 auto label = ui::Label( "Mapping mode" );
279 gtk_table_attach( table2, GTK_WIDGET( label ), 0, 1, 3, 4,
280 (GtkAttachOptions) ( GTK_FILL ),
281 (GtkAttachOptions) ( 0 ), 0, 0 );
282 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
284 auto combo = ui::ComboBoxText(ui::New);
285 gtk_combo_box_text_append_text( combo, globalMappingMode().sp_mapping_mode );
286 gtk_combo_box_text_append_text( combo, globalMappingMode().mp_mapping_mode );
289 gtk_table_attach( table2, GTK_WIDGET( combo ), 1, 2, 3, 4,
290 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
291 (GtkAttachOptions) ( 0 ), 0, 0 );
293 dialog.gamemode_combo = combo;
299 // initialise the fs_game selection from the project settings into the dialog
300 const char* dir = gamename_get();
301 gamecombo_t gamecombo = gamecombo_for_dir( dir );
303 gtk_combo_box_set_active( dialog.game_combo.game_select, gamecombo.game );
304 dialog.game_combo.fsgame_entry.text( gamecombo.fs_game );
305 gtk_widget_set_sensitive( GTK_WIDGET( dialog.game_combo.fsgame_entry ), gamecombo.sensitive );
307 if ( globalMappingMode().do_mapping_mode ) {
308 const char *gamemode = gamemode_get();
309 if ( string_empty( gamemode ) || string_equal( gamemode, "sp" ) ) {
310 gtk_combo_box_set_active( dialog.gamemode_combo, 0 );
314 gtk_combo_box_set_active( dialog.gamemode_combo, 1 );
321 void ProjectSettingsDialog_ok( ProjectSettingsDialog& dialog ){
322 const char* dir = gtk_entry_get_text( dialog.game_combo.fsgame_entry );
324 const char* new_gamename = path_equal( dir, globalGameComboConfiguration().basegame_dir )
328 if ( !path_equal( new_gamename, gamename_get() ) ) {
329 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Changing Game Name" );
331 EnginePath_Unrealise();
333 gamename_set( new_gamename );
335 EnginePath_Realise();
338 if ( globalMappingMode().do_mapping_mode ) {
339 // read from gamemode_combo
340 int active = gtk_combo_box_get_active( dialog.gamemode_combo );
341 if ( active == -1 || active == 0 ) {
342 gamemode_set( "sp" );
346 gamemode_set( "mp" );
351 void DoProjectSettings(){
352 if ( ConfirmModified( "Edit Project Settings" ) ) {
354 ProjectSettingsDialog dialog;
356 ui::Window window = ProjectSettingsDialog_construct( dialog, modal );
358 if ( modal_dialog_show( window, modal ) == eIDOK ) {
359 ProjectSettingsDialog_ok( dialog );
362 gtk_widget_destroy( GTK_WIDGET( window ) );
366 // =============================================================================
367 // Arbitrary Sides dialog
369 void DoSides( int type, int axis ){
371 GtkEntry* sides_entry;
373 auto window = MainFrame_getWindow().create_dialog_window("Arbitrary sides", G_CALLBACK(dialog_delete_callback ), &dialog );
375 auto accel = ui::AccelGroup(ui::New);
376 window.add_accel_group( accel );
379 auto hbox = create_dialog_hbox( 4, 4 );
382 auto label = ui::Label( "Sides:" );
384 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
387 auto entry = ui::Entry(ui::New);
389 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( entry ), FALSE, FALSE, 0 );
391 gtk_widget_grab_focus( GTK_WIDGET( entry ) );
394 GtkVBox* vbox = create_dialog_vbox( 4 );
395 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), TRUE, TRUE, 0 );
397 auto button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &dialog );
398 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
399 widget_make_default( button );
400 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
403 GtkButton* button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &dialog );
404 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
405 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
410 if ( modal_dialog_show( window, dialog ) == eIDOK ) {
411 const char *str = gtk_entry_get_text( sides_entry );
413 Scene_BrushConstructPrefab( GlobalSceneGraph(), (EBrushPrefab)type, atoi( str ), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
416 gtk_widget_destroy( GTK_WIDGET( window ) );
419 // =============================================================================
420 // About dialog (no program is complete without one)
422 void about_button_changelog( ui::Widget widget, gpointer data ){
423 StringOutputStream log( 256 );
424 log << "https://gitlab.com/xonotic/netradiant/commits/master";
425 OpenURL( log.c_str() );
428 void about_button_credits( ui::Widget widget, gpointer data ){
429 StringOutputStream cred( 256 );
430 cred << "https://gitlab.com/xonotic/netradiant/graphs/master";
431 OpenURL( cred.c_str() );
434 void about_button_issues( GtkWidget *widget, gpointer data ){
435 StringOutputStream cred( 256 );
436 cred << "https://gitlab.com/xonotic/netradiant/issues";
437 OpenURL( cred.c_str() );
442 ModalDialogButton ok_button( dialog, eIDOK );
444 auto window = MainFrame_getWindow().create_modal_dialog_window("About NetRadiant", dialog );
447 auto vbox = create_dialog_vbox( 4, 4 );
451 GtkHBox* hbox = create_dialog_hbox( 4 );
452 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), FALSE, TRUE, 0 );
455 GtkVBox* vbox2 = create_dialog_vbox( 4 );
456 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox2 ), TRUE, FALSE, 0 );
458 auto frame = create_dialog_frame( 0, ui::Shadow::IN );
459 gtk_box_pack_start( GTK_BOX( vbox2 ), GTK_WIDGET( frame ), FALSE, FALSE, 0 );
461 auto image = new_local_image( "logo.png" );
469 char const *label_text = "NetRadiant " RADIANT_VERSION "\n"
471 RADIANT_ABOUTMSG "\n\n"
472 "This program is free software\n"
473 "licensed under the GNU GPL.\n\n"
474 "NetRadiant is unsupported, however\n"
475 "you may report your problems at\n"
476 "https://gitlab.com/xonotic/netradiant/issues";
478 auto label = ui::Label( label_text );
481 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
482 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
483 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
487 GtkVBox* vbox2 = create_dialog_vbox( 4 );
488 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox2 ), FALSE, TRUE, 0 );
490 GtkButton* button = create_modal_dialog_button( "OK", ok_button );
491 gtk_box_pack_start( GTK_BOX( vbox2 ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
494 GtkButton* button = create_dialog_button( "Credits", G_CALLBACK( about_button_credits ), 0 );
495 gtk_box_pack_start( GTK_BOX( vbox2 ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
498 GtkButton* button = create_dialog_button( "Changes", G_CALLBACK( about_button_changelog ), 0 );
499 gtk_box_pack_start( GTK_BOX( vbox2 ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
502 GtkButton* button = create_dialog_button( "Issues", G_CALLBACK( about_button_issues ), 0 );
503 gtk_box_pack_start( GTK_BOX( vbox2 ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
508 auto frame = create_dialog_frame( "OpenGL Properties" );
509 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( frame ), FALSE, FALSE, 0 );
511 auto table = create_dialog_table( 3, 2, 4, 4, 4 );
514 auto label = ui::Label( "Vendor:" );
516 gtk_table_attach( table, GTK_WIDGET( label ), 0, 1, 0, 1,
517 (GtkAttachOptions) ( GTK_FILL ),
518 (GtkAttachOptions) ( 0 ), 0, 0 );
519 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
522 auto label = ui::Label( "Version:" );
524 gtk_table_attach( table, GTK_WIDGET( label ), 0, 1, 1, 2,
525 (GtkAttachOptions) ( GTK_FILL ),
526 (GtkAttachOptions) ( 0 ), 0, 0 );
527 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
530 auto label = ui::Label( "Renderer:" );
532 gtk_table_attach( table, GTK_WIDGET( label ), 0, 1, 2, 3,
533 (GtkAttachOptions) ( GTK_FILL ),
534 (GtkAttachOptions) ( 0 ), 0, 0 );
535 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
538 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VENDOR ) ) );
540 gtk_table_attach( table, GTK_WIDGET( label ), 1, 2, 0, 1,
541 (GtkAttachOptions) ( GTK_FILL ),
542 (GtkAttachOptions) ( 0 ), 0, 0 );
543 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
546 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VERSION ) ) );
548 gtk_table_attach( table, GTK_WIDGET( label ), 1, 2, 1, 2,
549 (GtkAttachOptions) ( GTK_FILL ),
550 (GtkAttachOptions) ( 0 ), 0, 0 );
551 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
554 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_RENDERER ) ) );
556 gtk_table_attach( table, GTK_WIDGET( label ), 1, 2, 2, 3,
557 (GtkAttachOptions) ( GTK_FILL ),
558 (GtkAttachOptions) ( 0 ), 0, 0 );
559 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
563 auto frame = create_dialog_frame( "OpenGL Extensions" );
564 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( frame ), TRUE, TRUE, 0 );
566 auto sc_extensions = create_scrolled_window( ui::Policy::AUTOMATIC, ui::Policy::ALWAYS, 4 );
567 frame.add(sc_extensions);
569 auto text_extensions = ui::TextView(ui::New);
570 gtk_text_view_set_editable( GTK_TEXT_VIEW( text_extensions ), FALSE );
571 sc_extensions.add(text_extensions);
572 text_extensions.text(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)));
573 gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW( text_extensions ), GTK_WRAP_WORD );
574 text_extensions.show();
581 modal_dialog_show( window, dialog );
583 gtk_widget_destroy( GTK_WIDGET( window ) );
586 // =============================================================================
587 // TextureLayout dialog
589 // Last used texture scale values
590 static float last_used_texture_layout_scale_x = 4.0;
591 static float last_used_texture_layout_scale_y = 4.0;
593 EMessageBoxReturn DoTextureLayout( float *fx, float *fy ){
595 ModalDialogButton ok_button( dialog, eIDOK );
596 ModalDialogButton cancel_button( dialog, eIDCANCEL );
597 ui::Entry x{ui::null};
598 ui::Entry y{ui::null};
600 auto window = MainFrame_getWindow().create_modal_dialog_window("Patch texture layout", dialog );
602 auto accel = ui::AccelGroup(ui::New);
603 window.add_accel_group( accel );
606 auto hbox = create_dialog_hbox( 4, 4 );
609 GtkVBox* vbox = create_dialog_vbox( 4 );
610 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), TRUE, TRUE, 0 );
612 auto label = ui::Label( "Texture will be fit across the patch based\n"
613 "on the x and y values given. Values of 1x1\n"
614 "will \"fit\" the texture. 2x2 will repeat\n"
617 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), TRUE, TRUE, 0 );
618 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
621 auto table = create_dialog_table( 2, 2, 4, 4 );
623 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
625 auto label = ui::Label( "Texture x:" );
627 gtk_table_attach( table, GTK_WIDGET( label ), 0, 1, 0, 1,
628 (GtkAttachOptions) ( GTK_FILL ),
629 (GtkAttachOptions) ( 0 ), 0, 0 );
630 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
633 auto label = ui::Label( "Texture y:" );
635 gtk_table_attach( table, GTK_WIDGET( label ), 0, 1, 1, 2,
636 (GtkAttachOptions) ( GTK_FILL ),
637 (GtkAttachOptions) ( 0 ), 0, 0 );
638 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
641 auto entry = ui::Entry(ui::New);
643 gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 0, 1,
644 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
645 (GtkAttachOptions) ( 0 ), 0, 0 );
650 auto entry = ui::Entry(ui::New);
652 gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 1, 2,
653 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
654 (GtkAttachOptions) ( 0 ), 0, 0 );
661 GtkVBox* vbox = create_dialog_vbox( 4 );
662 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), FALSE, FALSE, 0 );
664 auto button = create_modal_dialog_button( "OK", ok_button );
665 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
666 widget_make_default( button );
667 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
670 GtkButton* button = create_modal_dialog_button( "Cancel", cancel_button );
671 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
672 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
677 // Initialize with last used values
680 sprintf( buf, "%f", last_used_texture_layout_scale_x );
683 sprintf( buf, "%f", last_used_texture_layout_scale_y );
686 // Set focus after intializing the values
687 gtk_widget_grab_focus( GTK_WIDGET( x ) );
689 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
690 if ( ret == eIDOK ) {
691 *fx = static_cast<float>( atof( gtk_entry_get_text( x ) ) );
692 *fy = static_cast<float>( atof( gtk_entry_get_text( y ) ) );
694 // Remember last used values
695 last_used_texture_layout_scale_x = *fx;
696 last_used_texture_layout_scale_y = *fy;
699 gtk_widget_destroy( GTK_WIDGET( window ) );
704 // =============================================================================
705 // Text Editor dialog
707 // master window widget
708 static ui::Widget text_editor{ui::null};
709 static ui::Widget text_widget{ui::null}; // slave, text widget from the gtk editor
711 static gint editor_delete( ui::Widget widget, gpointer data ){
712 if ( widget.alert( "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
716 gtk_widget_hide( text_editor );
721 static void editor_save( ui::Widget widget, gpointer data ){
722 FILE *f = fopen( (char*)g_object_get_data( G_OBJECT( data ), "filename" ), "w" );
723 gpointer text = g_object_get_data( G_OBJECT( data ), "text" );
726 ui::Widget(GTK_WIDGET( data )).alert( "Error saving file !" );
730 char *str = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
731 fwrite( str, 1, strlen( str ), f );
735 static void editor_close( ui::Widget widget, gpointer data ){
736 if ( text_editor.alert( "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
740 gtk_widget_hide( text_editor );
743 static void CreateGtkTextEditor(){
744 auto dlg = ui::Window( ui::window_type::TOP );
746 dlg.connect( "delete_event",
747 G_CALLBACK( editor_delete ), 0 );
748 gtk_window_set_default_size( GTK_WINDOW( dlg ), 600, 300 );
750 auto vbox = ui::VBox( FALSE, 5 );
753 gtk_container_set_border_width( GTK_CONTAINER( vbox ), 5 );
755 auto scr = ui::ScrolledWindow(ui::New);
757 gtk_box_pack_start( GTK_BOX( vbox ), scr, TRUE, TRUE, 0 );
758 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scr ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
759 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( scr ), GTK_SHADOW_IN );
761 auto text = ui::TextView(ui::New);
764 g_object_set_data( G_OBJECT( dlg ), "text", (gpointer) text );
765 gtk_text_view_set_editable( GTK_TEXT_VIEW( text ), TRUE );
767 auto hbox = ui::HBox( FALSE, 5 );
769 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), FALSE, TRUE, 0 );
771 auto button = ui::Button( "Close" );
773 gtk_box_pack_end( GTK_BOX( hbox ), button, FALSE, FALSE, 0 );
774 button.connect( "clicked",
775 G_CALLBACK( editor_close ), dlg );
776 gtk_widget_set_size_request( button, 60, -1 );
778 button = ui::Button( "Save" );
780 gtk_box_pack_end( GTK_BOX( hbox ), button, FALSE, FALSE, 0 );
781 button.connect( "clicked",
782 G_CALLBACK( editor_save ), dlg );
783 gtk_widget_set_size_request( button, 60, -1 );
789 static void DoGtkTextEditor( const char* filename, guint cursorpos ){
790 if ( !text_editor ) {
791 CreateGtkTextEditor(); // build it the first time we need it
795 FILE *f = fopen( filename, "r" );
798 globalOutputStream() << "Unable to load file " << filename << " in shader editor.\n";
799 gtk_widget_hide( text_editor );
803 fseek( f, 0, SEEK_END );
804 int len = ftell( f );
805 void *buf = malloc( len );
809 fread( buf, 1, len, f );
811 gtk_window_set_title( GTK_WINDOW( text_editor ), filename );
813 GtkTextBuffer* text_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW( text_widget ) );
814 gtk_text_buffer_set_text( text_buffer, (char*)buf, len );
816 old_filename = g_object_get_data( G_OBJECT( text_editor ), "filename" );
817 if ( old_filename ) {
818 free( old_filename );
820 g_object_set_data( G_OBJECT( text_editor ), "filename", strdup( filename ) );
822 // trying to show later
829 // only move the cursor if it's not exceeding the size..
830 // NOTE: this is erroneous, cursorpos is the offset in bytes, not in characters
831 // len is the max size in bytes, not in characters either, but the character count is below that limit..
832 // thinking .. the difference between character count and byte count would be only because of CR/LF?
834 GtkTextIter text_iter;
835 // character offset, not byte offset
836 gtk_text_buffer_get_iter_at_offset( text_buffer, &text_iter, cursorpos );
837 gtk_text_buffer_place_cursor( text_buffer, &text_iter );
841 gtk_widget_queue_draw( text_widget );
849 // =============================================================================
850 // Light Intensity dialog
852 EMessageBoxReturn DoLightIntensityDlg( int *intensity ){
854 ui::Entry intensity_entry{ui::null};
855 ModalDialogButton ok_button( dialog, eIDOK );
856 ModalDialogButton cancel_button( dialog, eIDCANCEL );
858 ui::Window window = MainFrame_getWindow().create_modal_dialog_window("Light intensity", dialog, -1, -1 );
860 auto accel_group = ui::AccelGroup(ui::New);
861 window.add_accel_group( accel_group );
864 auto hbox = create_dialog_hbox( 4, 4 );
867 GtkVBox* vbox = create_dialog_vbox( 4 );
868 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), TRUE, TRUE, 0 );
870 auto label = ui::Label( "ESC for default, ENTER to validate" );
872 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
875 auto entry = ui::Entry(ui::New);
877 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( entry ), TRUE, TRUE, 0 );
879 gtk_widget_grab_focus( GTK_WIDGET( entry ) );
881 intensity_entry = entry;
885 GtkVBox* vbox = create_dialog_vbox( 4 );
886 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), FALSE, FALSE, 0 );
889 auto button = create_modal_dialog_button( "OK", ok_button );
890 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
891 widget_make_default( button );
892 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
895 GtkButton* button = create_modal_dialog_button( "Cancel", cancel_button );
896 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
897 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
903 sprintf( buf, "%d", *intensity );
904 intensity_entry.text(buf);
906 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
907 if ( ret == eIDOK ) {
908 *intensity = atoi( gtk_entry_get_text( intensity_entry ) );
911 gtk_widget_destroy( GTK_WIDGET( window ) );
916 // =============================================================================
917 // Add new shader tag dialog
919 EMessageBoxReturn DoShaderTagDlg( CopiedString* tag, const char* title ){
922 ModalDialogButton ok_button( dialog, eIDOK );
923 ModalDialogButton cancel_button( dialog, eIDCANCEL );
925 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
927 auto accel_group = ui::AccelGroup(ui::New);
928 window.add_accel_group( accel_group );
931 auto hbox = create_dialog_hbox( 4, 4 );
934 GtkVBox* vbox = create_dialog_vbox( 4 );
935 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), TRUE, TRUE, 0 );
937 //GtkLabel* label = GTK_LABEL(gtk_label_new("Enter one ore more tags separated by spaces"));
938 auto label = ui::Label( "ESC to cancel, ENTER to validate" );
940 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
943 auto entry = ui::Entry(ui::New);
945 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( entry ), TRUE, TRUE, 0 );
947 gtk_widget_grab_focus( GTK_WIDGET( entry ) );
953 GtkVBox* vbox = create_dialog_vbox( 4 );
954 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), FALSE, FALSE, 0 );
957 auto button = create_modal_dialog_button( "OK", ok_button );
958 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
959 widget_make_default( button );
960 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
963 GtkButton* button = create_modal_dialog_button( "Cancel", cancel_button );
964 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
965 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
970 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
971 if ( ret == eIDOK ) {
972 *tag = gtk_entry_get_text( textentry );
975 gtk_widget_destroy( GTK_WIDGET( window ) );
980 EMessageBoxReturn DoShaderInfoDlg( const char* name, const char* filename, const char* title ){
982 ModalDialogButton ok_button( dialog, eIDOK );
984 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
986 auto accel_group = ui::AccelGroup(ui::New);
987 window.add_accel_group( accel_group );
990 auto hbox = create_dialog_hbox( 4, 4 );
993 GtkVBox* vbox = create_dialog_vbox( 4 );
994 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), FALSE, FALSE, 0 );
996 auto label = ui::Label( "The selected shader" );
998 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
1001 auto label = ui::Label( name );
1003 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
1006 auto label = ui::Label( "is located in file" );
1008 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
1011 auto label = ui::Label( filename );
1013 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
1016 auto button = create_modal_dialog_button( "OK", ok_button );
1017 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
1018 widget_make_default( button );
1019 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
1024 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
1026 gtk_widget_destroy( GTK_WIDGET( window ) );
1034 #include <gdk/gdkwin32.h>
1038 // use the file associations to open files instead of builtin Gtk editor
1039 bool g_TextEditor_useWin32Editor = true;
1041 // custom shader editor
1042 bool g_TextEditor_useCustomEditor = false;
1043 CopiedString g_TextEditor_editorCommand( "" );
1046 void DoTextEditor( const char* filename, int cursorpos ){
1048 if ( g_TextEditor_useWin32Editor ) {
1049 globalOutputStream() << "opening file '" << filename << "' (line " << cursorpos << " info ignored)\n";
1050 ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", filename, 0, 0, SW_SHOW );
1054 // check if a custom editor is set
1055 if ( g_TextEditor_useCustomEditor && !g_TextEditor_editorCommand.empty() ) {
1056 StringOutputStream strEditCommand( 256 );
1057 strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << filename << "\"";
1059 globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n";
1060 // note: linux does not return false if the command failed so it will assume success
1061 if ( Q_Exec( 0, const_cast<char*>( strEditCommand.c_str() ), 0, true, false ) == false ) {
1062 globalOutputStream() << "Failed to execute " << strEditCommand.c_str() << ", using default\n";
1066 // the command (appeared) to run successfully, no need to do anything more
1072 DoGtkTextEditor( filename, cursorpos );