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>
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"
75 // =============================================================================
76 // Project settings dialog
78 class GameComboConfiguration
81 const char* basegame_dir;
83 const char* known_dir;
87 GameComboConfiguration() :
88 basegame_dir( g_pGameDescription->getRequiredKeyValue( "basegame" ) ),
89 basegame( g_pGameDescription->getRequiredKeyValue( "basegamename" ) ),
90 known_dir( g_pGameDescription->getKeyValue( "knowngame" ) ),
91 known( g_pGameDescription->getKeyValue( "knowngamename" ) ),
92 custom( g_pGameDescription->getRequiredKeyValue( "unknowngamename" ) ){
96 typedef LazyStatic<GameComboConfiguration> LazyStaticGameComboConfiguration;
98 inline GameComboConfiguration& globalGameComboConfiguration(){
99 return LazyStaticGameComboConfiguration::instance();
105 gamecombo_t( int _game, const char* _fs_game, bool _sensitive )
106 : game( _game ), fs_game( _fs_game ), sensitive( _sensitive )
113 gamecombo_t gamecombo_for_dir( const char* dir ){
114 if ( string_equal( dir, globalGameComboConfiguration().basegame_dir ) ) {
115 return gamecombo_t( 0, "", false );
117 else if ( string_equal( dir, globalGameComboConfiguration().known_dir ) ) {
118 return gamecombo_t( 1, dir, false );
122 return gamecombo_t( string_empty( globalGameComboConfiguration().known_dir ) ? 1 : 2, dir, true );
126 gamecombo_t gamecombo_for_gamename( const char* gamename ){
127 if ( ( strlen( gamename ) == 0 ) || !strcmp( gamename, globalGameComboConfiguration().basegame ) ) {
128 return gamecombo_t( 0, "", false );
130 else if ( !strcmp( gamename, globalGameComboConfiguration().known ) ) {
131 return gamecombo_t( 1, globalGameComboConfiguration().known_dir, false );
135 return gamecombo_t( string_empty( globalGameComboConfiguration().known_dir ) ? 1 : 2, "", true );
139 inline void path_copy_clean( char* destination, const char* source ){
140 char* i = destination;
142 while ( *source != '\0' )
144 *i++ = ( *source == '\\' ) ? '/' : *source;
148 if ( i != destination && *( i - 1 ) != '/' ) {
158 ui::ComboBoxText game_select{ui::null};
159 ui::Entry fsgame_entry{ui::null};
162 gboolean OnSelchangeComboWhatgame( ui::Widget widget, GameCombo* combo ){
163 const char *gamename;
166 gtk_combo_box_get_active_iter( combo->game_select, &iter );
167 gtk_tree_model_get( gtk_combo_box_get_model( combo->game_select ), &iter, 0, (gpointer*)&gamename, -1 );
170 gamecombo_t gamecombo = gamecombo_for_gamename( gamename );
172 combo->fsgame_entry.text( gamecombo.fs_game );
173 gtk_widget_set_sensitive( GTK_WIDGET( combo->fsgame_entry ), gamecombo.sensitive );
181 bool do_mapping_mode;
182 const char* sp_mapping_mode;
183 const char* mp_mapping_mode;
186 do_mapping_mode( !string_empty( g_pGameDescription->getKeyValue( "show_gamemode" ) ) ),
187 sp_mapping_mode( "Single Player mapping mode" ),
188 mp_mapping_mode( "Multiplayer mapping mode" ){
192 typedef LazyStatic<MappingMode> LazyStaticMappingMode;
194 inline MappingMode& globalMappingMode(){
195 return LazyStaticMappingMode::instance();
198 class ProjectSettingsDialog
201 GameCombo game_combo;
202 GtkComboBox* gamemode_combo;
205 ui::Window ProjectSettingsDialog_construct( ProjectSettingsDialog& dialog, ModalDialog& modal ){
206 auto window = MainFrame_getWindow().create_dialog_window("Project Settings", G_CALLBACK(dialog_delete_callback ), &modal );
209 auto table1 = create_dialog_table( 1, 2, 4, 4, 4 );
212 GtkVBox* vbox = create_dialog_vbox( 4 );
213 gtk_table_attach( table1, GTK_WIDGET( vbox ), 1, 2, 0, 1,
214 (GtkAttachOptions) ( GTK_FILL ),
215 (GtkAttachOptions) ( GTK_FILL ), 0, 0 );
217 GtkButton* button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &modal );
218 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
221 GtkButton* button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &modal );
222 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
226 auto frame = create_dialog_frame( "Project settings" );
227 gtk_table_attach( table1, GTK_WIDGET( frame ), 0, 1, 0, 1,
228 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
229 (GtkAttachOptions) ( GTK_FILL ), 0, 0 );
231 auto table2 = create_dialog_table( ( globalMappingMode().do_mapping_mode ) ? 4 : 3, 2, 4, 4, 4 );
235 auto label = ui::Label( "Select mod" );
237 gtk_table_attach( table2, GTK_WIDGET( label ), 0, 1, 0, 1,
238 (GtkAttachOptions) ( GTK_FILL ),
239 (GtkAttachOptions) ( 0 ), 0, 0 );
240 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
243 dialog.game_combo.game_select = ui::ComboBoxText(ui::New);
245 gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().basegame );
246 if ( globalGameComboConfiguration().known[0] != '\0' ) {
247 gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().known );
249 gtk_combo_box_text_append_text( dialog.game_combo.game_select, globalGameComboConfiguration().custom );
251 dialog.game_combo.game_select.show();
252 gtk_table_attach( table2, GTK_WIDGET( dialog.game_combo.game_select ), 1, 2, 0, 1,
253 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
254 (GtkAttachOptions) ( 0 ), 0, 0 );
256 dialog.game_combo.game_select.connect( "changed", G_CALLBACK( OnSelchangeComboWhatgame ), &dialog.game_combo );
260 auto label = ui::Label( "fs_game" );
262 gtk_table_attach( table2, GTK_WIDGET( label ), 0, 1, 1, 2,
263 (GtkAttachOptions) ( GTK_FILL ),
264 (GtkAttachOptions) ( 0 ), 0, 0 );
265 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
268 auto entry = ui::Entry(ui::New);
270 gtk_table_attach( table2, GTK_WIDGET( entry ), 1, 2, 1, 2,
271 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
272 (GtkAttachOptions) ( 0 ), 0, 0 );
274 dialog.game_combo.fsgame_entry = entry;
277 if ( globalMappingMode().do_mapping_mode ) {
278 auto label = ui::Label( "Mapping mode" );
280 gtk_table_attach( table2, GTK_WIDGET( label ), 0, 1, 3, 4,
281 (GtkAttachOptions) ( GTK_FILL ),
282 (GtkAttachOptions) ( 0 ), 0, 0 );
283 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
285 auto combo = ui::ComboBoxText(ui::New);
286 gtk_combo_box_text_append_text( combo, globalMappingMode().sp_mapping_mode );
287 gtk_combo_box_text_append_text( combo, globalMappingMode().mp_mapping_mode );
290 gtk_table_attach( table2, GTK_WIDGET( combo ), 1, 2, 3, 4,
291 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
292 (GtkAttachOptions) ( 0 ), 0, 0 );
294 dialog.gamemode_combo = combo;
300 // initialise the fs_game selection from the project settings into the dialog
301 const char* dir = gamename_get();
302 gamecombo_t gamecombo = gamecombo_for_dir( dir );
304 gtk_combo_box_set_active( dialog.game_combo.game_select, gamecombo.game );
305 dialog.game_combo.fsgame_entry.text( gamecombo.fs_game );
306 gtk_widget_set_sensitive( GTK_WIDGET( dialog.game_combo.fsgame_entry ), gamecombo.sensitive );
308 if ( globalMappingMode().do_mapping_mode ) {
309 const char *gamemode = gamemode_get();
310 if ( string_empty( gamemode ) || string_equal( gamemode, "sp" ) ) {
311 gtk_combo_box_set_active( dialog.gamemode_combo, 0 );
315 gtk_combo_box_set_active( dialog.gamemode_combo, 1 );
322 void ProjectSettingsDialog_ok( ProjectSettingsDialog& dialog ){
323 const char* dir = gtk_entry_get_text( dialog.game_combo.fsgame_entry );
325 const char* new_gamename = path_equal( dir, globalGameComboConfiguration().basegame_dir )
329 if ( !path_equal( new_gamename, gamename_get() ) ) {
330 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Changing Game Name" );
332 EnginePath_Unrealise();
334 gamename_set( new_gamename );
336 EnginePath_Realise();
339 if ( globalMappingMode().do_mapping_mode ) {
340 // read from gamemode_combo
341 int active = gtk_combo_box_get_active( dialog.gamemode_combo );
342 if ( active == -1 || active == 0 ) {
343 gamemode_set( "sp" );
347 gamemode_set( "mp" );
352 void DoProjectSettings(){
353 if ( ConfirmModified( "Edit Project Settings" ) ) {
355 ProjectSettingsDialog dialog;
357 ui::Window window = ProjectSettingsDialog_construct( dialog, modal );
359 if ( modal_dialog_show( window, modal ) == eIDOK ) {
360 ProjectSettingsDialog_ok( dialog );
367 // =============================================================================
368 // Arbitrary Sides dialog
370 void DoSides( int type, int axis ){
372 GtkEntry* sides_entry;
374 auto window = MainFrame_getWindow().create_dialog_window("Arbitrary sides", G_CALLBACK(dialog_delete_callback ), &dialog );
376 auto accel = ui::AccelGroup(ui::New);
377 window.add_accel_group( accel );
380 auto hbox = create_dialog_hbox( 4, 4 );
383 auto label = ui::Label( "Sides:" );
385 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
388 auto entry = ui::Entry(ui::New);
390 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( entry ), FALSE, FALSE, 0 );
392 gtk_widget_grab_focus( GTK_WIDGET( entry ) );
395 GtkVBox* vbox = create_dialog_vbox( 4 );
396 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), TRUE, TRUE, 0 );
398 auto button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &dialog );
399 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
400 widget_make_default( button );
401 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
404 GtkButton* button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &dialog );
405 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
406 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
411 if ( modal_dialog_show( window, dialog ) == eIDOK ) {
412 const char *str = gtk_entry_get_text( sides_entry );
414 Scene_BrushConstructPrefab( GlobalSceneGraph(), (EBrushPrefab)type, atoi( str ), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
420 // =============================================================================
421 // About dialog (no program is complete without one)
423 void about_button_changelog( ui::Widget widget, gpointer data ){
424 StringOutputStream log( 256 );
425 log << "https://gitlab.com/xonotic/netradiant/commits/master";
426 OpenURL( log.c_str() );
429 void about_button_credits( ui::Widget widget, gpointer data ){
430 StringOutputStream cred( 256 );
431 cred << "https://gitlab.com/xonotic/netradiant/graphs/master";
432 OpenURL( cred.c_str() );
435 void about_button_issues( GtkWidget *widget, gpointer data ){
436 StringOutputStream cred( 256 );
437 cred << "https://gitlab.com/xonotic/netradiant/issues";
438 OpenURL( cred.c_str() );
443 ModalDialogButton ok_button( dialog, eIDOK );
445 auto window = MainFrame_getWindow().create_modal_dialog_window("About NetRadiant", dialog );
448 auto vbox = create_dialog_vbox( 4, 4 );
452 GtkHBox* hbox = create_dialog_hbox( 4 );
453 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), FALSE, TRUE, 0 );
456 GtkVBox* vbox2 = create_dialog_vbox( 4 );
457 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox2 ), TRUE, FALSE, 0 );
459 auto frame = create_dialog_frame( 0, ui::Shadow::IN );
460 gtk_box_pack_start( GTK_BOX( vbox2 ), GTK_WIDGET( frame ), FALSE, FALSE, 0 );
462 auto image = new_local_image( "logo.png" );
470 char const *label_text = "NetRadiant " RADIANT_VERSION "\n"
472 RADIANT_ABOUTMSG "\n\n"
473 "This program is free software\n"
474 "licensed under the GNU GPL.\n\n"
475 "NetRadiant is unsupported, however\n"
476 "you may report your problems at\n"
477 "https://gitlab.com/xonotic/netradiant/issues";
479 auto label = ui::Label( label_text );
482 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
483 gtk_misc_set_alignment( GTK_MISC( label ), 1, 0.5 );
484 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
488 GtkVBox* vbox2 = create_dialog_vbox( 4 );
489 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox2 ), FALSE, TRUE, 0 );
491 GtkButton* button = create_modal_dialog_button( "OK", ok_button );
492 gtk_box_pack_start( GTK_BOX( vbox2 ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
495 GtkButton* button = create_dialog_button( "Credits", G_CALLBACK( about_button_credits ), 0 );
496 gtk_box_pack_start( GTK_BOX( vbox2 ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
499 GtkButton* button = create_dialog_button( "Changes", G_CALLBACK( about_button_changelog ), 0 );
500 gtk_box_pack_start( GTK_BOX( vbox2 ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
503 GtkButton* button = create_dialog_button( "Issues", G_CALLBACK( about_button_issues ), 0 );
504 gtk_box_pack_start( GTK_BOX( vbox2 ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
509 auto frame = create_dialog_frame( "OpenGL Properties" );
510 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( frame ), FALSE, FALSE, 0 );
512 auto table = create_dialog_table( 3, 2, 4, 4, 4 );
515 auto label = ui::Label( "Vendor:" );
517 gtk_table_attach( table, GTK_WIDGET( label ), 0, 1, 0, 1,
518 (GtkAttachOptions) ( GTK_FILL ),
519 (GtkAttachOptions) ( 0 ), 0, 0 );
520 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
523 auto label = ui::Label( "Version:" );
525 gtk_table_attach( table, GTK_WIDGET( label ), 0, 1, 1, 2,
526 (GtkAttachOptions) ( GTK_FILL ),
527 (GtkAttachOptions) ( 0 ), 0, 0 );
528 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
531 auto label = ui::Label( "Renderer:" );
533 gtk_table_attach( table, GTK_WIDGET( label ), 0, 1, 2, 3,
534 (GtkAttachOptions) ( GTK_FILL ),
535 (GtkAttachOptions) ( 0 ), 0, 0 );
536 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
539 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VENDOR ) ) );
541 gtk_table_attach( table, GTK_WIDGET( label ), 1, 2, 0, 1,
542 (GtkAttachOptions) ( GTK_FILL ),
543 (GtkAttachOptions) ( 0 ), 0, 0 );
544 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
547 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VERSION ) ) );
549 gtk_table_attach( table, GTK_WIDGET( label ), 1, 2, 1, 2,
550 (GtkAttachOptions) ( GTK_FILL ),
551 (GtkAttachOptions) ( 0 ), 0, 0 );
552 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
555 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_RENDERER ) ) );
557 gtk_table_attach( table, GTK_WIDGET( label ), 1, 2, 2, 3,
558 (GtkAttachOptions) ( GTK_FILL ),
559 (GtkAttachOptions) ( 0 ), 0, 0 );
560 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
564 auto frame = create_dialog_frame( "OpenGL Extensions" );
565 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( frame ), TRUE, TRUE, 0 );
567 auto sc_extensions = create_scrolled_window( ui::Policy::AUTOMATIC, ui::Policy::ALWAYS, 4 );
568 frame.add(sc_extensions);
570 auto text_extensions = ui::TextView(ui::New);
571 gtk_text_view_set_editable( GTK_TEXT_VIEW( text_extensions ), FALSE );
572 sc_extensions.add(text_extensions);
573 text_extensions.text(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)));
574 gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW( text_extensions ), GTK_WRAP_WORD );
575 text_extensions.show();
582 modal_dialog_show( window, dialog );
587 // =============================================================================
588 // TextureLayout dialog
590 // Last used texture scale values
591 static float last_used_texture_layout_scale_x = 4.0;
592 static float last_used_texture_layout_scale_y = 4.0;
594 EMessageBoxReturn DoTextureLayout( float *fx, float *fy ){
596 ModalDialogButton ok_button( dialog, eIDOK );
597 ModalDialogButton cancel_button( dialog, eIDCANCEL );
598 ui::Entry x{ui::null};
599 ui::Entry y{ui::null};
601 auto window = MainFrame_getWindow().create_modal_dialog_window("Patch texture layout", dialog );
603 auto accel = ui::AccelGroup(ui::New);
604 window.add_accel_group( accel );
607 auto hbox = create_dialog_hbox( 4, 4 );
610 GtkVBox* vbox = create_dialog_vbox( 4 );
611 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), TRUE, TRUE, 0 );
613 auto label = ui::Label( "Texture will be fit across the patch based\n"
614 "on the x and y values given. Values of 1x1\n"
615 "will \"fit\" the texture. 2x2 will repeat\n"
618 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), TRUE, TRUE, 0 );
619 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
622 auto table = create_dialog_table( 2, 2, 4, 4 );
624 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
626 auto label = ui::Label( "Texture x:" );
628 gtk_table_attach( table, GTK_WIDGET( label ), 0, 1, 0, 1,
629 (GtkAttachOptions) ( GTK_FILL ),
630 (GtkAttachOptions) ( 0 ), 0, 0 );
631 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
634 auto label = ui::Label( "Texture y:" );
636 gtk_table_attach( table, GTK_WIDGET( label ), 0, 1, 1, 2,
637 (GtkAttachOptions) ( GTK_FILL ),
638 (GtkAttachOptions) ( 0 ), 0, 0 );
639 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
642 auto entry = ui::Entry(ui::New);
644 gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 0, 1,
645 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
646 (GtkAttachOptions) ( 0 ), 0, 0 );
651 auto entry = ui::Entry(ui::New);
653 gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 1, 2,
654 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
655 (GtkAttachOptions) ( 0 ), 0, 0 );
662 GtkVBox* vbox = create_dialog_vbox( 4 );
663 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), FALSE, FALSE, 0 );
665 auto button = create_modal_dialog_button( "OK", ok_button );
666 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
667 widget_make_default( button );
668 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
671 GtkButton* button = create_modal_dialog_button( "Cancel", cancel_button );
672 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
673 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
678 // Initialize with last used values
681 sprintf( buf, "%f", last_used_texture_layout_scale_x );
684 sprintf( buf, "%f", last_used_texture_layout_scale_y );
687 // Set focus after intializing the values
688 gtk_widget_grab_focus( GTK_WIDGET( x ) );
690 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
691 if ( ret == eIDOK ) {
692 *fx = static_cast<float>( atof( gtk_entry_get_text( x ) ) );
693 *fy = static_cast<float>( atof( gtk_entry_get_text( y ) ) );
695 // Remember last used values
696 last_used_texture_layout_scale_x = *fx;
697 last_used_texture_layout_scale_y = *fy;
705 // =============================================================================
706 // Text Editor dialog
708 // master window widget
709 static ui::Widget text_editor{ui::null};
710 static ui::Widget text_widget{ui::null}; // slave, text widget from the gtk editor
712 static gint editor_delete( ui::Widget widget, gpointer data ){
713 if ( widget.alert( "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
722 static void editor_save( ui::Widget widget, gpointer data ){
723 FILE *f = fopen( (char*)g_object_get_data( G_OBJECT( data ), "filename" ), "w" );
724 gpointer text = g_object_get_data( G_OBJECT( data ), "text" );
727 ui::Widget(GTK_WIDGET( data )).alert( "Error saving file !" );
731 char *str = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
732 fwrite( str, 1, strlen( str ), f );
736 static void editor_close( ui::Widget widget, gpointer data ){
737 if ( text_editor.alert( "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
744 static void CreateGtkTextEditor(){
745 auto dlg = ui::Window( ui::window_type::TOP );
747 dlg.connect( "delete_event",
748 G_CALLBACK( editor_delete ), 0 );
749 gtk_window_set_default_size( GTK_WINDOW( dlg ), 600, 300 );
751 auto vbox = ui::VBox( FALSE, 5 );
754 gtk_container_set_border_width( GTK_CONTAINER( vbox ), 5 );
756 auto scr = ui::ScrolledWindow(ui::New);
758 gtk_box_pack_start( GTK_BOX( vbox ), scr, TRUE, TRUE, 0 );
759 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scr ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
760 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( scr ), GTK_SHADOW_IN );
762 auto text = ui::TextView(ui::New);
765 g_object_set_data( G_OBJECT( dlg ), "text", (gpointer) text );
766 gtk_text_view_set_editable( GTK_TEXT_VIEW( text ), TRUE );
768 auto hbox = ui::HBox( FALSE, 5 );
770 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), FALSE, TRUE, 0 );
772 auto button = ui::Button( "Close" );
774 gtk_box_pack_end( GTK_BOX( hbox ), button, FALSE, FALSE, 0 );
775 button.connect( "clicked",
776 G_CALLBACK( editor_close ), dlg );
777 gtk_widget_set_size_request( button, 60, -1 );
779 button = ui::Button( "Save" );
781 gtk_box_pack_end( GTK_BOX( hbox ), button, FALSE, FALSE, 0 );
782 button.connect( "clicked",
783 G_CALLBACK( editor_save ), dlg );
784 gtk_widget_set_size_request( button, 60, -1 );
790 static void DoGtkTextEditor( const char* filename, guint cursorpos ){
791 if ( !text_editor ) {
792 CreateGtkTextEditor(); // build it the first time we need it
796 FILE *f = fopen( filename, "r" );
799 globalOutputStream() << "Unable to load file " << filename << " in shader editor.\n";
804 fseek( f, 0, SEEK_END );
805 int len = ftell( f );
806 void *buf = malloc( len );
810 fread( buf, 1, len, f );
812 gtk_window_set_title( GTK_WINDOW( text_editor ), filename );
814 GtkTextBuffer* text_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW( text_widget ) );
815 gtk_text_buffer_set_text( text_buffer, (char*)buf, len );
817 old_filename = g_object_get_data( G_OBJECT( text_editor ), "filename" );
818 if ( old_filename ) {
819 free( old_filename );
821 g_object_set_data( G_OBJECT( text_editor ), "filename", strdup( filename ) );
823 // trying to show later
830 // only move the cursor if it's not exceeding the size..
831 // NOTE: this is erroneous, cursorpos is the offset in bytes, not in characters
832 // len is the max size in bytes, not in characters either, but the character count is below that limit..
833 // thinking .. the difference between character count and byte count would be only because of CR/LF?
835 GtkTextIter text_iter;
836 // character offset, not byte offset
837 gtk_text_buffer_get_iter_at_offset( text_buffer, &text_iter, cursorpos );
838 gtk_text_buffer_place_cursor( text_buffer, &text_iter );
842 gtk_widget_queue_draw( text_widget );
850 // =============================================================================
851 // Light Intensity dialog
853 EMessageBoxReturn DoLightIntensityDlg( int *intensity ){
855 ui::Entry intensity_entry{ui::null};
856 ModalDialogButton ok_button( dialog, eIDOK );
857 ModalDialogButton cancel_button( dialog, eIDCANCEL );
859 ui::Window window = MainFrame_getWindow().create_modal_dialog_window("Light intensity", dialog, -1, -1 );
861 auto accel_group = ui::AccelGroup(ui::New);
862 window.add_accel_group( accel_group );
865 auto hbox = create_dialog_hbox( 4, 4 );
868 GtkVBox* vbox = create_dialog_vbox( 4 );
869 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), TRUE, TRUE, 0 );
871 auto label = ui::Label( "ESC for default, ENTER to validate" );
873 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
876 auto entry = ui::Entry(ui::New);
878 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( entry ), TRUE, TRUE, 0 );
880 gtk_widget_grab_focus( GTK_WIDGET( entry ) );
882 intensity_entry = entry;
886 GtkVBox* vbox = create_dialog_vbox( 4 );
887 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), FALSE, FALSE, 0 );
890 auto button = create_modal_dialog_button( "OK", ok_button );
891 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
892 widget_make_default( button );
893 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
896 GtkButton* button = create_modal_dialog_button( "Cancel", cancel_button );
897 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
898 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
904 sprintf( buf, "%d", *intensity );
905 intensity_entry.text(buf);
907 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
908 if ( ret == eIDOK ) {
909 *intensity = atoi( gtk_entry_get_text( intensity_entry ) );
917 // =============================================================================
918 // Add new shader tag dialog
920 EMessageBoxReturn DoShaderTagDlg( CopiedString* tag, const char* title ){
923 ModalDialogButton ok_button( dialog, eIDOK );
924 ModalDialogButton cancel_button( dialog, eIDCANCEL );
926 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
928 auto accel_group = ui::AccelGroup(ui::New);
929 window.add_accel_group( accel_group );
932 auto hbox = create_dialog_hbox( 4, 4 );
935 GtkVBox* vbox = create_dialog_vbox( 4 );
936 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), TRUE, TRUE, 0 );
938 //GtkLabel* label = GTK_LABEL(gtk_label_new("Enter one ore more tags separated by spaces"));
939 auto label = ui::Label( "ESC to cancel, ENTER to validate" );
941 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
944 auto entry = ui::Entry(ui::New);
946 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( entry ), TRUE, TRUE, 0 );
948 gtk_widget_grab_focus( GTK_WIDGET( entry ) );
954 GtkVBox* vbox = create_dialog_vbox( 4 );
955 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), FALSE, FALSE, 0 );
958 auto button = create_modal_dialog_button( "OK", ok_button );
959 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
960 widget_make_default( button );
961 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
964 GtkButton* button = create_modal_dialog_button( "Cancel", cancel_button );
965 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
966 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
971 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
972 if ( ret == eIDOK ) {
973 *tag = gtk_entry_get_text( textentry );
981 EMessageBoxReturn DoShaderInfoDlg( const char* name, const char* filename, const char* title ){
983 ModalDialogButton ok_button( dialog, eIDOK );
985 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
987 auto accel_group = ui::AccelGroup(ui::New);
988 window.add_accel_group( accel_group );
991 auto hbox = create_dialog_hbox( 4, 4 );
994 GtkVBox* vbox = create_dialog_vbox( 4 );
995 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox ), FALSE, FALSE, 0 );
997 auto label = ui::Label( "The selected shader" );
999 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
1002 auto label = ui::Label( name );
1004 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
1007 auto label = ui::Label( "is located in file" );
1009 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
1012 auto label = ui::Label( filename );
1014 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, FALSE, 0 );
1017 auto button = create_modal_dialog_button( "OK", ok_button );
1018 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
1019 widget_make_default( button );
1020 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
1025 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
1035 #include <gdk/gdkwin32.h>
1039 // use the file associations to open files instead of builtin Gtk editor
1040 bool g_TextEditor_useWin32Editor = true;
1042 // custom shader editor
1043 bool g_TextEditor_useCustomEditor = false;
1044 CopiedString g_TextEditor_editorCommand( "" );
1047 void DoTextEditor( const char* filename, int cursorpos ){
1049 if ( g_TextEditor_useWin32Editor ) {
1050 globalOutputStream() << "opening file '" << filename << "' (line " << cursorpos << " info ignored)\n";
1051 ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", filename, 0, 0, SW_SHOW );
1055 // check if a custom editor is set
1056 if ( g_TextEditor_useCustomEditor && !g_TextEditor_editorCommand.empty() ) {
1057 StringOutputStream strEditCommand( 256 );
1058 strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << filename << "\"";
1060 globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n";
1061 // note: linux does not return false if the command failed so it will assume success
1062 if ( Q_Exec( 0, const_cast<char*>( strEditCommand.c_str() ), 0, true, false ) == false ) {
1063 globalOutputStream() << "Failed to execute " << strEditCommand.c_str() << ", using default\n";
1067 // the command (appeared) to run successfully, no need to do anything more
1073 DoGtkTextEditor( filename, cursorpos );