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 );
546 auto button = create_dialog_button( "Changes", G_CALLBACK( about_button_changelog ), 0 );
547 vbox2.pack_start( button, FALSE, FALSE, 0 );
550 auto button = create_dialog_button( "Issues", G_CALLBACK( about_button_issues ), 0 );
551 vbox2.pack_start( button, FALSE, FALSE, 0 );
556 auto frame = create_dialog_frame( "OpenGL Properties" );
557 vbox.pack_start( frame, FALSE, FALSE, 0 );
559 auto table = create_dialog_table( 3, 2, 4, 4, 4 );
562 auto label = ui::Label( "Vendor:" );
564 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
565 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
568 auto label = ui::Label( "Version:" );
570 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
571 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
574 auto label = ui::Label( "Renderer:" );
576 table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0});
577 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
580 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VENDOR ) ) );
582 table.attach(label, {1, 2, 0, 1}, {GTK_FILL, 0});
583 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
586 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_VERSION ) ) );
588 table.attach(label, {1, 2, 1, 2}, {GTK_FILL, 0});
589 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
592 auto label = ui::Label( reinterpret_cast<const char*>( glGetString( GL_RENDERER ) ) );
594 table.attach(label, {1, 2, 2, 3}, {GTK_FILL, 0});
595 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
599 auto frame = create_dialog_frame( "OpenGL Extensions" );
600 vbox.pack_start( frame, TRUE, TRUE, 0 );
602 auto sc_extensions = create_scrolled_window( ui::Policy::AUTOMATIC, ui::Policy::ALWAYS, 4 );
603 frame.add(sc_extensions);
605 auto text_extensions = ui::TextView(ui::New);
606 gtk_text_view_set_editable( text_extensions, FALSE );
607 sc_extensions.add(text_extensions);
608 text_extensions.text(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)));
609 gtk_text_view_set_wrap_mode( text_extensions, GTK_WRAP_WORD );
610 text_extensions.show();
617 modal_dialog_show( window, dialog );
622 // =============================================================================
623 // TextureLayout dialog
625 // Last used texture scale values
626 static float last_used_texture_layout_scale_x = 4.0;
627 static float last_used_texture_layout_scale_y = 4.0;
629 EMessageBoxReturn DoTextureLayout( float *fx, float *fy ){
631 ModalDialogButton ok_button( dialog, eIDOK );
632 ModalDialogButton cancel_button( dialog, eIDCANCEL );
633 ui::Entry x{ui::null};
634 ui::Entry y{ui::null};
636 auto window = MainFrame_getWindow().create_modal_dialog_window("Patch texture layout", dialog );
638 auto accel = ui::AccelGroup(ui::New);
639 window.add_accel_group( accel );
642 auto hbox = create_dialog_hbox( 4, 4 );
645 auto vbox = create_dialog_vbox( 4 );
646 hbox.pack_start( vbox, TRUE, TRUE, 0 );
648 auto label = ui::Label( "Texture will be fit across the patch based\n"
649 "on the x and y values given. Values of 1x1\n"
650 "will \"fit\" the texture. 2x2 will repeat\n"
653 vbox.pack_start( label, TRUE, TRUE, 0 );
654 gtk_label_set_justify( label, GTK_JUSTIFY_LEFT );
657 auto table = create_dialog_table( 2, 2, 4, 4 );
659 vbox.pack_start( table, TRUE, TRUE, 0 );
661 auto label = ui::Label( "Texture x:" );
663 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
664 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
667 auto label = ui::Label( "Texture y:" );
669 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
670 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
673 auto entry = ui::Entry(ui::New);
675 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
680 auto entry = ui::Entry(ui::New);
682 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
689 auto vbox = create_dialog_vbox( 4 );
690 hbox.pack_start( vbox, FALSE, FALSE, 0 );
692 auto button = create_modal_dialog_button( "OK", ok_button );
693 vbox.pack_start( button, FALSE, FALSE, 0 );
694 widget_make_default( button );
695 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
698 auto button = create_modal_dialog_button( "Cancel", cancel_button );
699 vbox.pack_start( button, FALSE, FALSE, 0 );
700 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
705 // Initialize with last used values
708 sprintf( buf, "%f", last_used_texture_layout_scale_x );
711 sprintf( buf, "%f", last_used_texture_layout_scale_y );
714 // Set focus after intializing the values
715 gtk_widget_grab_focus( x );
717 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
718 if ( ret == eIDOK ) {
719 *fx = static_cast<float>( atof( gtk_entry_get_text( x ) ) );
720 *fy = static_cast<float>( atof( gtk_entry_get_text( y ) ) );
722 // Remember last used values
723 last_used_texture_layout_scale_x = *fx;
724 last_used_texture_layout_scale_y = *fy;
732 // =============================================================================
733 // Text Editor dialog
735 // master window widget
736 static ui::Window text_editor{ui::null};
737 static ui::Widget text_widget{ui::null}; // slave, text widget from the gtk editor
738 static GtkTextBuffer* text_buffer_;
740 static gint editor_delete( ui::Widget widget, gpointer data ){
741 /* if ( ui::alert( widget.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
750 static void editor_save( ui::Widget widget, gpointer data ){
751 FILE *f = fopen( (char*)g_object_get_data( G_OBJECT( data ), "filename" ), "w" );
752 //gpointer text = g_object_get_data( G_OBJECT( data ), "text" );
755 ui::alert( ui::Widget::from(data).window(), "Error saving file !" );
759 /* Obtain iters for the start and end of points of the buffer */
762 gtk_text_buffer_get_start_iter (text_buffer_, &start);
763 gtk_text_buffer_get_end_iter (text_buffer_, &end);
765 /* Get the entire buffer text. */
766 char *str = gtk_text_buffer_get_text (text_buffer_, &start, &end, FALSE);
768 //char *str = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
769 fwrite( str, 1, strlen( str ), f );
774 static void editor_close( ui::Widget widget, gpointer data ){
775 /* if ( ui::alert( text_editor.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
782 static void CreateGtkTextEditor(){
783 auto dlg = ui::Window( ui::window_type::TOP );
785 dlg.connect( "", G_CALLBACK( editor_delete ), 0 );
786 gtk_window_set_default_size( dlg, 400, 600 );
788 auto vbox = ui::VBox( FALSE, 5 );
791 gtk_container_set_border_width( GTK_CONTAINER( vbox ), 5 );
793 auto scr = ui::ScrolledWindow(ui::New);
795 vbox.pack_start( scr, TRUE, TRUE, 0 );
796 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scr ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
797 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( scr ), GTK_SHADOW_IN );
799 auto text = ui::TextView(ui::New);
802 g_object_set_data( G_OBJECT( dlg ), "text", (gpointer) text );
803 gtk_text_view_set_editable( text, TRUE );
805 auto hbox = ui::HBox( FALSE, 5 );
807 vbox.pack_start( hbox, FALSE, TRUE, 0 );
809 auto button = ui::Button( "Close" );
811 hbox.pack_end(button, FALSE, FALSE, 0);
812 button.connect( "clicked",
813 G_CALLBACK( editor_close ), dlg );
814 button.dimensions(60, -1);
816 button = ui::Button( "Save" );
818 hbox.pack_end(button, FALSE, FALSE, 0);
819 button.connect( "clicked",
820 G_CALLBACK( editor_save ), dlg );
821 button.dimensions(60, -1);
827 static void DoGtkTextEditor( const char* filename, guint cursorpos, int length ){
828 if ( !text_editor ) {
829 CreateGtkTextEditor(); // build it the first time we need it
833 FILE *f = fopen( filename, "r" );
836 globalOutputStream() << "Unable to load file " << filename << " in shader editor.\n";
841 fseek( f, 0, SEEK_END );
842 int len = ftell( f );
843 void *buf = malloc( len );
847 fread( buf, 1, len, f );
849 gtk_window_set_title( text_editor, filename );
851 auto text_buffer = gtk_text_view_get_buffer(ui::TextView::from(text_widget));
852 gtk_text_buffer_set_text( text_buffer, (char*)buf, length );
854 old_filename = g_object_get_data( G_OBJECT( text_editor ), "filename" );
855 if ( old_filename ) {
856 free( old_filename );
858 g_object_set_data( G_OBJECT( text_editor ), "filename", strdup( filename ) );
860 // trying to show later
862 gtk_window_present( GTK_WINDOW( text_editor ) );
864 //#if GDEF_OS_WINDOWS
868 // only move the cursor if it's not exceeding the size..
869 // NOTE: this is erroneous, cursorpos is the offset in bytes, not in characters
870 // len is the max size in bytes, not in characters either, but the character count is below that limit..
871 // thinking .. the difference between character count and byte count would be only because of CR/LF?
873 GtkTextIter text_iter;
874 // character offset, not byte offset
875 gtk_text_buffer_get_iter_at_offset( text_buffer, &text_iter, cursorpos );
876 gtk_text_buffer_place_cursor( text_buffer, &text_iter );
877 gtk_text_view_scroll_to_iter( GTK_TEXT_VIEW( text_widget ), &text_iter, 0, TRUE, 0, 0);
880 //#if GDEF_OS_WINDOWS
881 gtk_widget_queue_draw( text_widget );
884 text_buffer_ = text_buffer;
890 // =============================================================================
891 // Light Intensity dialog
893 EMessageBoxReturn DoLightIntensityDlg( int *intensity ){
895 ui::Entry intensity_entry{ui::null};
896 ModalDialogButton ok_button( dialog, eIDOK );
897 ModalDialogButton cancel_button( dialog, eIDCANCEL );
899 ui::Window window = MainFrame_getWindow().create_modal_dialog_window("Light intensity", dialog, -1, -1 );
901 auto accel_group = ui::AccelGroup(ui::New);
902 window.add_accel_group( accel_group );
905 auto hbox = create_dialog_hbox( 4, 4 );
908 auto vbox = create_dialog_vbox( 4 );
909 hbox.pack_start( vbox, TRUE, TRUE, 0 );
911 auto label = ui::Label( "ESC for default, ENTER to validate" );
913 vbox.pack_start( label, FALSE, FALSE, 0 );
916 auto entry = ui::Entry(ui::New);
918 vbox.pack_start( entry, TRUE, TRUE, 0 );
920 gtk_widget_grab_focus( entry );
922 intensity_entry = entry;
926 auto vbox = create_dialog_vbox( 4 );
927 hbox.pack_start( vbox, FALSE, FALSE, 0 );
930 auto button = create_modal_dialog_button( "OK", ok_button );
931 vbox.pack_start( button, FALSE, FALSE, 0 );
932 widget_make_default( button );
933 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
936 auto button = create_modal_dialog_button( "Cancel", cancel_button );
937 vbox.pack_start( button, FALSE, FALSE, 0 );
938 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
944 sprintf( buf, "%d", *intensity );
945 intensity_entry.text(buf);
947 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
948 if ( ret == eIDOK ) {
949 *intensity = atoi( gtk_entry_get_text( intensity_entry ) );
957 // =============================================================================
958 // Add new shader tag dialog
960 EMessageBoxReturn DoShaderTagDlg( CopiedString* tag, const char* title ){
962 ModalDialogButton ok_button( dialog, eIDOK );
963 ModalDialogButton cancel_button( dialog, eIDCANCEL );
965 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
967 auto accel_group = ui::AccelGroup(ui::New);
968 window.add_accel_group( accel_group );
970 auto textentry = ui::Entry(ui::New);
972 auto hbox = create_dialog_hbox( 4, 4 );
975 auto vbox = create_dialog_vbox( 4 );
976 hbox.pack_start( vbox, TRUE, TRUE, 0 );
978 //GtkLabel* label = GTK_LABEL(gtk_label_new("Enter one ore more tags separated by spaces"));
979 auto label = ui::Label( "ESC to cancel, ENTER to validate" );
981 vbox.pack_start( label, FALSE, FALSE, 0 );
984 auto entry = textentry;
986 vbox.pack_start( entry, TRUE, TRUE, 0 );
988 gtk_widget_grab_focus( entry );
992 auto vbox = create_dialog_vbox( 4 );
993 hbox.pack_start( vbox, FALSE, FALSE, 0 );
996 auto button = create_modal_dialog_button( "OK", ok_button );
997 vbox.pack_start( button, FALSE, FALSE, 0 );
998 widget_make_default( button );
999 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
1002 auto button = create_modal_dialog_button( "Cancel", cancel_button );
1003 vbox.pack_start( button, FALSE, FALSE, 0 );
1004 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
1009 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
1010 if ( ret == eIDOK ) {
1011 *tag = gtk_entry_get_text( textentry );
1019 EMessageBoxReturn DoShaderInfoDlg( const char* name, const char* filename, const char* title ){
1021 ModalDialogButton ok_button( dialog, eIDOK );
1023 auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1 );
1025 auto accel_group = ui::AccelGroup(ui::New);
1026 window.add_accel_group( accel_group );
1029 auto hbox = create_dialog_hbox( 4, 4 );
1032 auto vbox = create_dialog_vbox( 4 );
1033 hbox.pack_start( vbox, FALSE, FALSE, 0 );
1035 auto label = ui::Label( "The selected shader" );
1037 vbox.pack_start( label, FALSE, FALSE, 0 );
1040 auto label = ui::Label( name );
1042 vbox.pack_start( label, FALSE, FALSE, 0 );
1045 auto label = ui::Label( "is located in file" );
1047 vbox.pack_start( label, FALSE, FALSE, 0 );
1050 auto label = ui::Label( filename );
1052 vbox.pack_start( label, FALSE, FALSE, 0 );
1055 auto button = create_modal_dialog_button( "OK", ok_button );
1056 vbox.pack_start( button, FALSE, FALSE, 0 );
1057 widget_make_default( button );
1058 gtk_widget_add_accelerator( button , "clicked", accel_group, GDK_KEY_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE );
1063 EMessageBoxReturn ret = modal_dialog_show( window, dialog );
1073 #include <gdk/gdkwin32.h>
1076 CopiedString g_TextEditor_editorCommand( "" );
1078 void DoTextEditor( const char* filename, int cursorpos, int length, bool external_editor ){
1079 //StringOutputStream paths[4]( 256 );
1080 StringOutputStream paths[4] = { StringOutputStream(256) };
1081 StringOutputStream* goodpath = 0;
1083 const char* gamename = GlobalRadiant().getGameName();
1084 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1085 const char* enginePath = GlobalRadiant().getEnginePath();
1086 const char* homeEnginePath = g_qeglobals.m_userEnginePath.c_str();
1088 paths[0] << homeEnginePath << gamename << '/' << filename;
1089 paths[1] << enginePath << gamename << '/' << filename;
1090 paths[2] << homeEnginePath << basegame << '/' << filename;
1091 paths[3] << enginePath << basegame << '/' << filename;
1093 for ( std::size_t i = 0; i < 4; ++i ){
1094 if ( file_exists( paths[i].c_str() ) ){
1095 goodpath = &paths[i];
1101 globalOutputStream() << "opening file '" << goodpath->c_str() << "' (line " << cursorpos << " info ignored)\n";
1102 if( external_editor ){
1103 if( g_TextEditor_editorCommand.empty() ){
1105 ShellExecute( (HWND)GDK_WINDOW_HWND( GTK_WIDGET( MainFrame_getWindow() )->window ), 0, goodpath->c_str(), 0, 0, SW_SHOWNORMAL );
1106 // SHELLEXECUTEINFO ShExecInfo;
1107 // ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
1108 // ShExecInfo.fMask = 0;
1109 // ShExecInfo.hwnd = (HWND)GDK_WINDOW_HWND( GTK_WIDGET( MainFrame_getWindow() )->window );
1110 // ShExecInfo.lpVerb = NULL;
1111 // ShExecInfo.lpFile = goodpath->c_str();
1112 // ShExecInfo.lpParameters = NULL;
1113 // ShExecInfo.lpDirectory = NULL;
1114 // ShExecInfo.nShow = SW_SHOWNORMAL;
1115 // ShExecInfo.hInstApp = NULL;
1116 // ShellExecuteEx(&ShExecInfo);
1118 globalOutputStream() << "Failed to open '" << goodpath->c_str() << "'\nSet Shader Editor Command in preferences\n";
1122 StringOutputStream strEditCommand( 256 );
1123 strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << goodpath->c_str() << "\"";
1125 globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n";
1126 // note: linux does not return false if the command failed so it will assume success
1127 if ( Q_Exec( 0, const_cast<char*>( strEditCommand.c_str() ), 0, true, false ) == false ) {
1128 globalOutputStream() << "Failed to execute " << strEditCommand.c_str() << "\n";
1132 // the command (appeared) to run successfully, no need to do anything more
1138 DoGtkTextEditor( goodpath->c_str(), cursorpos, length );
1142 globalOutputStream() << "Failed to open '" << filename << "'\nOne sits in .pk3 most likely!\n";