]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/preferences.cpp
Remove RTLD_DEEPBIND, it is no longer required
[xonotic/netradiant.git] / radiant / preferences.cpp
1 /*
2    Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3    For a list of contributors, see the accompanying CONTRIBUTORS file.
4
5    This file is part of GtkRadiant.
6
7    GtkRadiant is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    GtkRadiant is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with GtkRadiant; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21
22 //
23 // User preferences
24 //
25 // Leonardo Zide (leo@lokigames.com)
26 //
27
28 #include "preferences.h"
29
30 #include <gtk/gtk.h>
31 #include "environment.h"
32
33 #include "debugging/debugging.h"
34
35 #include "generic/callback.h"
36 #include "math/vector.h"
37 #include "string/string.h"
38 #include "stream/stringstream.h"
39 #include "os/file.h"
40 #include "os/path.h"
41 #include "os/dir.h"
42 #include "gtkutil/filechooser.h"
43 #include "gtkutil/messagebox.h"
44 #include "cmdlib.h"
45
46 #include "error.h"
47 #include "console.h"
48 #include "xywindow.h"
49 #include "mainframe.h"
50 #include "qe3.h"
51 #include "gtkdlgs.h"
52
53
54
55 void Global_constructPreferences( PreferencesPage& page ){
56         page.appendCheckBox( "Console", "Enable Logging", g_Console_enableLogging );
57 }
58
59 void Interface_constructPreferences( PreferencesPage& page ){
60 #ifdef WIN32
61         page.appendCheckBox( "", "Default Text Editor", g_TextEditor_useWin32Editor );
62 #else
63         {
64                 ui::CheckButton use_custom = page.appendCheckBox( "Text Editor", "Custom", g_TextEditor_useCustomEditor );
65                 ui::Widget custom_editor = page.appendPathEntry( "Text Editor Command", g_TextEditor_editorCommand, true );
66                 Widget_connectToggleDependency( custom_editor, use_custom );
67         }
68 #endif
69 }
70
71 void Mouse_constructPreferences( PreferencesPage& page ){
72         {
73                 const char* buttons[] = { "2 button", "3 button", };
74                 page.appendRadio( "Mouse Type",  g_glwindow_globals.m_nMouseType, STRING_ARRAY_RANGE( buttons ) );
75         }
76         page.appendCheckBox( "Right Button", "Activates Context Menu", g_xywindow_globals.m_bRightClick );
77 }
78 void Mouse_constructPage( PreferenceGroup& group ){
79         PreferencesPage page( group.createPage( "Mouse", "Mouse Preferences" ) );
80         Mouse_constructPreferences( page );
81 }
82 void Mouse_registerPreferencesPage(){
83         PreferencesDialog_addInterfacePage( FreeCaller1<PreferenceGroup&, Mouse_constructPage>() );
84 }
85
86
87 /*!
88    =========================================================
89    Games selection dialog
90    =========================================================
91  */
92
93 #include <map>
94 #include <uilib/uilib.h>
95
96 inline const char* xmlAttr_getName( xmlAttrPtr attr ){
97         return reinterpret_cast<const char*>( attr->name );
98 }
99
100 inline const char* xmlAttr_getValue( xmlAttrPtr attr ){
101         return reinterpret_cast<const char*>( attr->children->content );
102 }
103
104 CGameDescription::CGameDescription( xmlDocPtr pDoc, const CopiedString& gameFile ){
105         // read the user-friendly game name
106         xmlNodePtr pNode = pDoc->children;
107
108         while ( strcmp( (const char*)pNode->name, "game" ) && pNode != 0 )
109         {
110                 pNode = pNode->next;
111         }
112         if ( !pNode ) {
113                 Error( "Didn't find 'game' node in the game description file '%s'\n", pDoc->URL );
114         }
115
116         for ( xmlAttrPtr attr = pNode->properties; attr != 0; attr = attr->next )
117         {
118                 m_gameDescription.insert( GameDescription::value_type( xmlAttr_getName( attr ), xmlAttr_getValue( attr ) ) );
119         }
120
121         {
122                 StringOutputStream path( 256 );
123                 path << AppPath_get() << gameFile.c_str() << "/";
124                 mGameToolsPath = path.c_str();
125         }
126
127         ASSERT_MESSAGE( file_exists( mGameToolsPath.c_str() ), "game directory not found: " << makeQuoted( mGameToolsPath.c_str() ) );
128
129         mGameFile = gameFile;
130
131         {
132                 GameDescription::iterator i = m_gameDescription.find( "type" );
133                 if ( i == m_gameDescription.end() ) {
134                         globalErrorStream() << "Warning, 'type' attribute not found in '" << reinterpret_cast<const char*>( pDoc->URL ) << "'\n";
135                         // default
136                         mGameType = "q3";
137                 }
138                 else
139                 {
140                         mGameType = ( *i ).second.c_str();
141                 }
142         }
143 }
144
145 void CGameDescription::Dump(){
146         globalOutputStream() << "game description file: " << makeQuoted( mGameFile.c_str() ) << "\n";
147         for ( GameDescription::iterator i = m_gameDescription.begin(); i != m_gameDescription.end(); ++i )
148         {
149                 globalOutputStream() << ( *i ).first.c_str() << " = " << makeQuoted( ( *i ).second.c_str() ) << "\n";
150         }
151 }
152
153 CGameDescription *g_pGameDescription; ///< shortcut to g_GamesDialog.m_pCurrentDescription
154
155
156 #include "warnings.h"
157 #include "stream/textfilestream.h"
158 #include "container/array.h"
159 #include "xml/ixml.h"
160 #include "xml/xmlparser.h"
161 #include "xml/xmlwriter.h"
162
163 #include "preferencedictionary.h"
164 #include "stringio.h"
165
166 const char* const PREFERENCES_VERSION = "1.0";
167
168 bool Preferences_Load( PreferenceDictionary& preferences, const char* filename, const char *cmdline_prefix ){
169         bool ret = false;
170         TextFileInputStream file( filename );
171         if ( !file.failed() ) {
172                 XMLStreamParser parser( file );
173                 XMLPreferenceDictionaryImporter importer( preferences, PREFERENCES_VERSION );
174                 parser.exportXML( importer );
175                 ret = true;
176         }
177
178         int l = strlen( cmdline_prefix );
179         for ( int i = 1; i < g_argc - 1; ++i )
180         {
181                 if ( g_argv[i][0] == '-' ) {
182                         if ( !strncmp( g_argv[i] + 1, cmdline_prefix, l ) ) {
183                                 if ( g_argv[i][l + 1] == '-' ) {
184                                         preferences.importPref( g_argv[i] + l + 2, g_argv[i + 1] );
185                                 }
186                         }
187                         ++i;
188                 }
189         }
190
191         return ret;
192 }
193
194 bool Preferences_Save( PreferenceDictionary& preferences, const char* filename ){
195         TextFileOutputStream file( filename );
196         if ( !file.failed() ) {
197                 XMLStreamWriter writer( file );
198                 XMLPreferenceDictionaryExporter exporter( preferences, PREFERENCES_VERSION );
199                 exporter.exportXML( writer );
200                 return true;
201         }
202         return false;
203 }
204
205 bool Preferences_Save_Safe( PreferenceDictionary& preferences, const char* filename ){
206         Array<char> tmpName( filename, filename + strlen( filename ) + 1 + 3 );
207         *( tmpName.end() - 4 ) = 'T';
208         *( tmpName.end() - 3 ) = 'M';
209         *( tmpName.end() - 2 ) = 'P';
210         *( tmpName.end() - 1 ) = '\0';
211
212         return Preferences_Save( preferences, tmpName.data() )
213                    && ( !file_exists( filename ) || file_remove( filename ) )
214                    && file_move( tmpName.data(), filename );
215 }
216
217
218
219 void LogConsole_importString( const char* string ){
220         g_Console_enableLogging = string_equal( string, "true" );
221         Sys_LogFile( g_Console_enableLogging );
222 }
223 typedef FreeCaller1<const char*, LogConsole_importString> LogConsoleImportStringCaller;
224
225
226 void RegisterGlobalPreferences( PreferenceSystem& preferences ){
227         preferences.registerPreference( "gamefile", CopiedStringImportStringCaller( g_GamesDialog.m_sGameFile ), CopiedStringExportStringCaller( g_GamesDialog.m_sGameFile ) );
228         preferences.registerPreference( "gamePrompt", BoolImportStringCaller( g_GamesDialog.m_bGamePrompt ), BoolExportStringCaller( g_GamesDialog.m_bGamePrompt ) );
229         preferences.registerPreference( "log console", LogConsoleImportStringCaller(), BoolExportStringCaller( g_Console_enableLogging ) );
230 }
231
232
233 PreferenceDictionary g_global_preferences;
234
235 void GlobalPreferences_Init(){
236         RegisterGlobalPreferences( g_global_preferences );
237 }
238
239 void CGameDialog::LoadPrefs(){
240         // load global .pref file
241         StringOutputStream strGlobalPref( 256 );
242         strGlobalPref << g_Preferences.m_global_rc_path->str << "global.pref";
243
244         globalOutputStream() << "loading global preferences from " << makeQuoted( strGlobalPref.c_str() ) << "\n";
245
246         if ( !Preferences_Load( g_global_preferences, strGlobalPref.c_str(), "global" ) ) {
247                 globalOutputStream() << "failed to load global preferences from " << strGlobalPref.c_str() << "\n";
248         }
249 }
250
251 void CGameDialog::SavePrefs(){
252         StringOutputStream strGlobalPref( 256 );
253         strGlobalPref << g_Preferences.m_global_rc_path->str << "global.pref";
254
255         globalOutputStream() << "saving global preferences to " << strGlobalPref.c_str() << "\n";
256
257         if ( !Preferences_Save_Safe( g_global_preferences, strGlobalPref.c_str() ) ) {
258                 globalOutputStream() << "failed to save global preferences to " << strGlobalPref.c_str() << "\n";
259         }
260 }
261
262 void CGameDialog::DoGameDialog(){
263         // show the UI
264         DoModal();
265
266         // we save the prefs file
267         SavePrefs();
268 }
269
270 void CGameDialog::GameFileImport( int value ){
271         m_nComboSelect = value;
272         // use value to set m_sGameFile
273         std::list<CGameDescription *>::iterator iGame = mGames.begin();
274         int i;
275         for ( i = 0; i < value; i++ )
276         {
277                 ++iGame;
278         }
279         m_sGameFile = ( *iGame )->mGameFile;
280 }
281
282 void CGameDialog::GameFileExport( const IntImportCallback& importCallback ) const {
283         // use m_sGameFile to set value
284         std::list<CGameDescription *>::const_iterator iGame;
285         int i = 0;
286         for ( iGame = mGames.begin(); iGame != mGames.end(); ++iGame )
287         {
288                 if ( ( *iGame )->mGameFile == m_sGameFile ) {
289                         m_nComboSelect = i;
290                         break;
291                 }
292                 i++;
293         }
294         importCallback( m_nComboSelect );
295 }
296
297 void CGameDialog_GameFileImport( CGameDialog& self, int value ){
298         self.GameFileImport( value );
299 }
300
301 void CGameDialog_GameFileExport( CGameDialog& self, const IntImportCallback& importCallback ){
302         self.GameFileExport( importCallback );
303 }
304
305 void CGameDialog::CreateGlobalFrame( PreferencesPage& page ){
306         std::vector<const char*> games;
307         games.reserve( mGames.size() );
308         for ( std::list<CGameDescription *>::iterator i = mGames.begin(); i != mGames.end(); ++i )
309         {
310                 games.push_back( ( *i )->getRequiredKeyValue( "name" ) );
311         }
312         page.appendCombo(
313                 "Select the game",
314                 StringArrayRange( &( *games.begin() ), &( *games.end() ) ),
315                 ReferenceCaller1<CGameDialog, int, CGameDialog_GameFileImport>( *this ),
316                 ReferenceCaller1<CGameDialog, const IntImportCallback&, CGameDialog_GameFileExport>( *this )
317                 );
318         page.appendCheckBox( "Startup", "Show Global Preferences", m_bGamePrompt );
319 }
320
321 ui::Window CGameDialog::BuildDialog(){
322         auto frame = create_dialog_frame( "Game settings", ui::Shadow::ETCHED_IN );
323
324         auto vbox2 = create_dialog_vbox( 0, 4 );
325         frame.add(vbox2);
326
327         {
328                 PreferencesPage preferencesPage( *this, ui::Widget(GTK_WIDGET( vbox2 )) );
329                 Global_constructPreferences( preferencesPage );
330                 CreateGlobalFrame( preferencesPage );
331         }
332
333         return create_simple_modal_dialog_window( "Global Preferences", m_modal, frame );
334 }
335
336 class LoadGameFile
337 {
338 std::list<CGameDescription*>& mGames;
339 const char* mPath;
340 public:
341 LoadGameFile( std::list<CGameDescription*>& games, const char* path ) : mGames( games ), mPath( path ){
342 }
343 void operator()( const char* name ) const {
344         if ( !extension_equal( path_get_extension( name ), "game" ) ) {
345                 return;
346         }
347         StringOutputStream strPath( 256 );
348         strPath << mPath << name;
349         globalOutputStream() << strPath.c_str() << '\n';
350
351         xmlDocPtr pDoc = xmlParseFile( strPath.c_str() );
352         if ( pDoc ) {
353                 mGames.push_front( new CGameDescription( pDoc, name ) );
354                 xmlFreeDoc( pDoc );
355         }
356         else
357         {
358                 globalErrorStream() << "XML parser failed on '" << strPath.c_str() << "'\n";
359         }
360 }
361 };
362
363 void CGameDialog::ScanForGames(){
364         StringOutputStream strGamesPath( 256 );
365         strGamesPath << AppPath_get() << "games/";
366         const char *path = strGamesPath.c_str();
367
368         globalOutputStream() << "Scanning for game description files: " << path << '\n';
369
370         /*!
371            \todo FIXME LINUX:
372            do we put game description files below AppPath, or in ~/.radiant
373            i.e. read only or read/write?
374            my guess .. readonly cause it's an install
375            we will probably want to add ~/.radiant/<version>/games/ scanning on top of that for developers
376            (if that's really needed)
377          */
378
379         Directory_forEach( path, LoadGameFile( mGames, path ) );
380 }
381
382 CGameDescription* CGameDialog::GameDescriptionForComboItem(){
383         std::list<CGameDescription *>::iterator iGame;
384         int i = 0;
385         for ( iGame = mGames.begin(); iGame != mGames.end(); ++iGame,i++ )
386         {
387                 if ( i == m_nComboSelect ) {
388                         return ( *iGame );
389                 }
390         }
391         return 0; // not found
392 }
393
394 void CGameDialog::InitGlobalPrefPath(){
395         g_Preferences.m_global_rc_path = g_string_new( SettingsPath_get() );
396 }
397
398 void CGameDialog::Reset(){
399         if ( !g_Preferences.m_global_rc_path ) {
400                 InitGlobalPrefPath();
401         }
402         StringOutputStream strGlobalPref( 256 );
403         strGlobalPref << g_Preferences.m_global_rc_path->str << "global.pref";
404         file_remove( strGlobalPref.c_str() );
405 }
406
407 void CGameDialog::Init(){
408         InitGlobalPrefPath();
409         LoadPrefs();
410         ScanForGames();
411         if ( mGames.empty() ) {
412                 Error( "Didn't find any valid game file descriptions, aborting\n" );
413         }
414         else
415         {
416                 std::list<CGameDescription *>::iterator iGame, iPrevGame;
417                 for ( iGame = mGames.begin(), iPrevGame = mGames.end(); iGame != mGames.end(); iPrevGame = iGame, ++iGame )
418                 {
419                         if ( iPrevGame != mGames.end() ) {
420                                 if ( strcmp( ( *iGame )->getRequiredKeyValue( "name" ), ( *iPrevGame )->getRequiredKeyValue( "name" ) ) < 0 ) {
421                                         CGameDescription *h = *iGame;
422                                         *iGame = *iPrevGame;
423                                         *iPrevGame = h;
424                                 }
425                         }
426                 }
427         }
428
429         CGameDescription* currentGameDescription = 0;
430
431         if ( !m_bGamePrompt ) {
432                 // search by .game name
433                 std::list<CGameDescription *>::iterator iGame;
434                 for ( iGame = mGames.begin(); iGame != mGames.end(); ++iGame )
435                 {
436                         if ( ( *iGame )->mGameFile == m_sGameFile ) {
437                                 currentGameDescription = ( *iGame );
438                                 break;
439                         }
440                 }
441         }
442         if ( m_bGamePrompt || !currentGameDescription ) {
443                 Create();
444                 DoGameDialog();
445                 // use m_nComboSelect to identify the game to run as and set the globals
446                 currentGameDescription = GameDescriptionForComboItem();
447                 ASSERT_NOTNULL( currentGameDescription );
448         }
449         g_pGameDescription = currentGameDescription;
450
451         g_pGameDescription->Dump();
452 }
453
454 CGameDialog::~CGameDialog(){
455         // free all the game descriptions
456         std::list<CGameDescription *>::iterator iGame;
457         for ( iGame = mGames.begin(); iGame != mGames.end(); ++iGame )
458         {
459                 delete ( *iGame );
460                 *iGame = 0;
461         }
462         if ( GetWidget() ) {
463                 Destroy();
464         }
465 }
466
467 inline const char* GameDescription_getIdentifier( const CGameDescription& gameDescription ){
468         const char* identifier = gameDescription.getKeyValue( "index" );
469         if ( string_empty( identifier ) ) {
470                 identifier = "1";
471         }
472         return identifier;
473 }
474
475
476 CGameDialog g_GamesDialog;
477
478
479 // =============================================================================
480 // Widget callbacks for PrefsDlg
481
482 static void OnButtonClean( ui::Widget widget, gpointer data ){
483         // make sure this is what the user wants
484         if ( ui::Widget(GTK_WIDGET( g_Preferences.GetWidget() )).alert( "This will close Radiant and clean the corresponding registry entries.\n"
485                                                                                                                                   "Next time you start Radiant it will be good as new. Do you wish to continue?",
486                                                  "Reset Registry", ui::alert_type::YESNO, ui::alert_icon::Asterisk ) == ui::alert_response::YES ) {
487                 PrefsDlg *dlg = (PrefsDlg*)data;
488                 dlg->EndModal( eIDCANCEL );
489
490                 g_preferences_globals.disable_ini = true;
491                 Preferences_Reset();
492                 gtk_main_quit();
493         }
494 }
495
496 // =============================================================================
497 // PrefsDlg class
498
499 /*
500    ========
501
502    very first prefs init deals with selecting the game and the game tools path
503    then we can load .ini stuff
504
505    using prefs / ini settings:
506    those are per-game
507
508    look in ~/.radiant/<version>/gamename
509    ========
510  */
511
512 #define PREFS_LOCAL_FILENAME "local.pref"
513
514 void PrefsDlg::Init(){
515         // m_global_rc_path has been set above
516         // m_rc_path is for game specific preferences
517         // takes the form: global-pref-path/gamename/prefs-file
518
519         // this is common to win32 and Linux init now
520         m_rc_path = g_string_new( m_global_rc_path->str );
521
522         // game sub-dir
523         g_string_append( m_rc_path, g_pGameDescription->mGameFile.c_str() );
524         g_string_append( m_rc_path, "/" );
525         Q_mkdir( m_rc_path->str );
526
527         // then the ini file
528         m_inipath = g_string_new( m_rc_path->str );
529         g_string_append( m_inipath, PREFS_LOCAL_FILENAME );
530 }
531
532 void notebook_set_page( ui::Widget notebook, ui::Widget page ){
533         int pagenum = gtk_notebook_page_num( GTK_NOTEBOOK( notebook ), page );
534         if ( gtk_notebook_get_current_page( GTK_NOTEBOOK( notebook ) ) != pagenum ) {
535                 gtk_notebook_set_current_page( GTK_NOTEBOOK( notebook ), pagenum );
536         }
537 }
538
539 void PrefsDlg::showPrefPage( ui::Widget prefpage ){
540         notebook_set_page( m_notebook, prefpage );
541         return;
542 }
543
544 static void treeSelection( GtkTreeSelection* selection, gpointer data ){
545         PrefsDlg *dlg = (PrefsDlg*)data;
546
547         GtkTreeModel* model;
548         GtkTreeIter selected;
549         if ( gtk_tree_selection_get_selected( selection, &model, &selected ) ) {
550                 ui::Widget prefpage{ui::null};
551                 gtk_tree_model_get( model, &selected, 1, (gpointer*)&prefpage, -1 );
552                 dlg->showPrefPage( prefpage );
553         }
554 }
555
556 typedef std::list<PreferenceGroupCallback> PreferenceGroupCallbacks;
557
558 inline void PreferenceGroupCallbacks_constructGroup( const PreferenceGroupCallbacks& callbacks, PreferenceGroup& group ){
559         for ( PreferenceGroupCallbacks::const_iterator i = callbacks.begin(); i != callbacks.end(); ++i )
560         {
561                 ( *i )( group );
562         }
563 }
564
565
566 inline void PreferenceGroupCallbacks_pushBack( PreferenceGroupCallbacks& callbacks, const PreferenceGroupCallback& callback ){
567         callbacks.push_back( callback );
568 }
569
570 typedef std::list<PreferencesPageCallback> PreferencesPageCallbacks;
571
572 inline void PreferencesPageCallbacks_constructPage( const PreferencesPageCallbacks& callbacks, PreferencesPage& page ){
573         for ( PreferencesPageCallbacks::const_iterator i = callbacks.begin(); i != callbacks.end(); ++i )
574         {
575                 ( *i )( page );
576         }
577 }
578
579 inline void PreferencesPageCallbacks_pushBack( PreferencesPageCallbacks& callbacks, const PreferencesPageCallback& callback ){
580         callbacks.push_back( callback );
581 }
582
583 PreferencesPageCallbacks g_interfacePreferences;
584 void PreferencesDialog_addInterfacePreferences( const PreferencesPageCallback& callback ){
585         PreferencesPageCallbacks_pushBack( g_interfacePreferences, callback );
586 }
587 PreferenceGroupCallbacks g_interfaceCallbacks;
588 void PreferencesDialog_addInterfacePage( const PreferenceGroupCallback& callback ){
589         PreferenceGroupCallbacks_pushBack( g_interfaceCallbacks, callback );
590 }
591
592 PreferencesPageCallbacks g_displayPreferences;
593 void PreferencesDialog_addDisplayPreferences( const PreferencesPageCallback& callback ){
594         PreferencesPageCallbacks_pushBack( g_displayPreferences, callback );
595 }
596 PreferenceGroupCallbacks g_displayCallbacks;
597 void PreferencesDialog_addDisplayPage( const PreferenceGroupCallback& callback ){
598         PreferenceGroupCallbacks_pushBack( g_displayCallbacks, callback );
599 }
600
601 PreferencesPageCallbacks g_settingsPreferences;
602 void PreferencesDialog_addSettingsPreferences( const PreferencesPageCallback& callback ){
603         PreferencesPageCallbacks_pushBack( g_settingsPreferences, callback );
604 }
605 PreferenceGroupCallbacks g_settingsCallbacks;
606 void PreferencesDialog_addSettingsPage( const PreferenceGroupCallback& callback ){
607         PreferenceGroupCallbacks_pushBack( g_settingsCallbacks, callback );
608 }
609
610 void Widget_updateDependency( ui::Widget self, ui::Widget toggleButton ){
611         gtk_widget_set_sensitive( self, gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( toggleButton ) ) && gtk_widget_is_sensitive( toggleButton ) );
612 }
613
614 void ToggleButton_toggled_Widget_updateDependency( ui::Widget toggleButton, ui::Widget self ){
615         Widget_updateDependency( self, toggleButton );
616 }
617
618 void ToggleButton_state_changed_Widget_updateDependency( ui::Widget toggleButton, GtkStateType state, ui::Widget self ){
619         if ( state == GTK_STATE_INSENSITIVE ) {
620                 Widget_updateDependency( self, toggleButton );
621         }
622 }
623
624 void Widget_connectToggleDependency( ui::Widget self, ui::Widget toggleButton ){
625         toggleButton.connect( "state_changed", G_CALLBACK( ToggleButton_state_changed_Widget_updateDependency ), self );
626         toggleButton.connect( "toggled", G_CALLBACK( ToggleButton_toggled_Widget_updateDependency ), self );
627         Widget_updateDependency( self, toggleButton );
628 }
629
630
631 inline ui::Widget getVBox( ui::Widget page ){
632         return ui::Widget(gtk_bin_get_child( GTK_BIN( page ) ));
633 }
634
635 GtkTreeIter PreferenceTree_appendPage( GtkTreeStore* store, GtkTreeIter* parent, const char* name, ui::Widget page ){
636         GtkTreeIter group;
637         gtk_tree_store_append( store, &group, parent );
638         gtk_tree_store_set( store, &group, 0, name, 1, page, -1 );
639         return group;
640 }
641
642 ui::Widget PreferencePages_addPage( ui::Widget notebook, const char* name ){
643         ui::Widget preflabel = ui::Label( name );
644         preflabel.show();
645
646         auto pageframe = ui::Frame( name );
647         gtk_container_set_border_width( GTK_CONTAINER( pageframe ), 4 );
648         pageframe.show();
649
650         ui::Widget vbox = ui::VBox( FALSE, 4 );
651         vbox.show();
652         gtk_container_set_border_width( GTK_CONTAINER( vbox ), 4 );
653         pageframe.add(vbox);
654
655         // Add the page to the notebook
656         gtk_notebook_append_page( GTK_NOTEBOOK( notebook ), pageframe, preflabel );
657
658         return pageframe;
659 }
660
661 class PreferenceTreeGroup : public PreferenceGroup
662 {
663 Dialog& m_dialog;
664 ui::Widget m_notebook;
665 GtkTreeStore* m_store;
666 GtkTreeIter m_group;
667 public:
668 PreferenceTreeGroup( Dialog& dialog, ui::Widget notebook, GtkTreeStore* store, GtkTreeIter group ) :
669         m_dialog( dialog ),
670         m_notebook( notebook ),
671         m_store( store ),
672         m_group( group ){
673 }
674 PreferencesPage createPage( const char* treeName, const char* frameName ){
675         ui::Widget page = PreferencePages_addPage( m_notebook, frameName );
676         PreferenceTree_appendPage( m_store, &m_group, treeName, page );
677         return PreferencesPage( m_dialog, getVBox( page ) );
678 }
679 };
680
681 ui::Window PrefsDlg::BuildDialog(){
682         PreferencesDialog_addInterfacePreferences( FreeCaller1<PreferencesPage&, Interface_constructPreferences>() );
683         Mouse_registerPreferencesPage();
684
685         ui::Window dialog = ui::Window(create_floating_window( "NetRadiant Preferences", m_parent ));
686
687         {
688                 ui::Widget mainvbox = ui::VBox( FALSE, 5 );
689                 dialog.add(mainvbox);
690                 gtk_container_set_border_width( GTK_CONTAINER( mainvbox ), 5 );
691                 mainvbox.show();
692
693                 {
694                         ui::Widget hbox = ui::HBox( FALSE, 5 );
695                         hbox.show();
696                         gtk_box_pack_end( GTK_BOX( mainvbox ), hbox, FALSE, TRUE, 0 );
697
698                         {
699                                 GtkButton* button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &m_modal );
700                                 gtk_box_pack_end( GTK_BOX( hbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
701                         }
702                         {
703                                 GtkButton* button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &m_modal );
704                                 gtk_box_pack_end( GTK_BOX( hbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
705                         }
706                         {
707                                 GtkButton* button = create_dialog_button( "Clean", G_CALLBACK( OnButtonClean ), this );
708                                 gtk_box_pack_end( GTK_BOX( hbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
709                         }
710                 }
711
712                 {
713                         ui::Widget hbox = ui::HBox( FALSE, 5 );
714                         gtk_box_pack_start( GTK_BOX( mainvbox ), hbox, TRUE, TRUE, 0 );
715                         hbox.show();
716
717                         {
718                                 auto sc_win = ui::ScrolledWindow(ui::New);
719                                 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( sc_win ), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC );
720                                 gtk_box_pack_start( GTK_BOX( hbox ), sc_win, FALSE, FALSE, 0 );
721                                 sc_win.show();
722                                 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( sc_win ), GTK_SHADOW_IN );
723
724                                 // prefs pages notebook
725                                 m_notebook = ui::Widget(gtk_notebook_new());
726                                 // hide the notebook tabs since its not supposed to look like a notebook
727                                 gtk_notebook_set_show_tabs( GTK_NOTEBOOK( m_notebook ), FALSE );
728                                 gtk_box_pack_start( GTK_BOX( hbox ), m_notebook, TRUE, TRUE, 0 );
729                                 m_notebook.show();
730
731
732                                 {
733                                         auto store = gtk_tree_store_new( 2, G_TYPE_STRING, G_TYPE_POINTER );
734
735                                         ui::Widget view = ui::TreeView(ui::TreeModel( GTK_TREE_MODEL( store ) ));
736                                         gtk_tree_view_set_headers_visible( GTK_TREE_VIEW( view ), FALSE );
737
738                                         {
739                                                 auto renderer = ui::CellRendererText(ui::New);
740                                                 GtkTreeViewColumn* column = ui::TreeViewColumn( "Preferences", renderer, {{"text", 0}} );
741                                                 gtk_tree_view_append_column( GTK_TREE_VIEW( view ), column );
742                                         }
743
744                                         {
745                                                 auto selection = ui::TreeSelection(gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) ));
746                                                 selection.connect( "changed", G_CALLBACK( treeSelection ), this );
747                                         }
748
749                                         view.show();
750
751                                         sc_win.add(view);
752
753                                         {
754                                                 /********************************************************************/
755                                                 /* Add preference tree options                                      */
756                                                 /********************************************************************/
757                                                 // Front page...
758                                                 //GtkWidget* front =
759                                                 PreferencePages_addPage( m_notebook, "Front Page" );
760
761                                                 {
762                                                         ui::Widget global = PreferencePages_addPage( m_notebook, "Global Preferences" );
763                                                         {
764                                                                 PreferencesPage preferencesPage( *this, getVBox( global ) );
765                                                                 Global_constructPreferences( preferencesPage );
766                                                         }
767                                                         GtkTreeIter group = PreferenceTree_appendPage( store, 0, "Global", global );
768                                                         {
769                                                                 ui::Widget game = PreferencePages_addPage( m_notebook, "Game" );
770                                                                 PreferencesPage preferencesPage( *this, getVBox( game ) );
771                                                                 g_GamesDialog.CreateGlobalFrame( preferencesPage );
772
773                                                                 PreferenceTree_appendPage( store, &group, "Game", game );
774                                                         }
775                                                 }
776
777                                                 {
778                                                         ui::Widget interfacePage = PreferencePages_addPage( m_notebook, "Interface Preferences" );
779                                                         {
780                                                                 PreferencesPage preferencesPage( *this, getVBox( interfacePage ) );
781                                                                 PreferencesPageCallbacks_constructPage( g_interfacePreferences, preferencesPage );
782                                                         }
783
784                                                         GtkTreeIter group = PreferenceTree_appendPage( store, 0, "Interface", interfacePage );
785                                                         PreferenceTreeGroup preferenceGroup( *this, m_notebook, store, group );
786
787                                                         PreferenceGroupCallbacks_constructGroup( g_interfaceCallbacks, preferenceGroup );
788                                                 }
789
790                                                 {
791                                                         ui::Widget display = PreferencePages_addPage( m_notebook, "Display Preferences" );
792                                                         {
793                                                                 PreferencesPage preferencesPage( *this, getVBox( display ) );
794                                                                 PreferencesPageCallbacks_constructPage( g_displayPreferences, preferencesPage );
795                                                         }
796                                                         GtkTreeIter group = PreferenceTree_appendPage( store, 0, "Display", display );
797                                                         PreferenceTreeGroup preferenceGroup( *this, m_notebook, store, group );
798
799                                                         PreferenceGroupCallbacks_constructGroup( g_displayCallbacks, preferenceGroup );
800                                                 }
801
802                                                 {
803                                                         ui::Widget settings = PreferencePages_addPage( m_notebook, "General Settings" );
804                                                         {
805                                                                 PreferencesPage preferencesPage( *this, getVBox( settings ) );
806                                                                 PreferencesPageCallbacks_constructPage( g_settingsPreferences, preferencesPage );
807                                                         }
808
809                                                         GtkTreeIter group = PreferenceTree_appendPage( store, 0, "Settings", settings );
810                                                         PreferenceTreeGroup preferenceGroup( *this, m_notebook, store, group );
811
812                                                         PreferenceGroupCallbacks_constructGroup( g_settingsCallbacks, preferenceGroup );
813                                                 }
814                                         }
815
816                                         gtk_tree_view_expand_all( GTK_TREE_VIEW( view ) );
817
818                                         g_object_unref( G_OBJECT( store ) );
819                                 }
820                         }
821                 }
822         }
823
824         gtk_notebook_set_current_page( GTK_NOTEBOOK( m_notebook ), 0 );
825
826         return dialog;
827 }
828
829 preferences_globals_t g_preferences_globals;
830
831 PrefsDlg g_Preferences;               // global prefs instance
832
833
834 void PreferencesDialog_constructWindow( ui::Window main_window ){
835         g_Preferences.m_parent = main_window;
836         g_Preferences.Create();
837 }
838 void PreferencesDialog_destroyWindow(){
839         g_Preferences.Destroy();
840 }
841
842
843 PreferenceDictionary g_preferences;
844
845 PreferenceSystem& GetPreferenceSystem(){
846         return g_preferences;
847 }
848
849 class PreferenceSystemAPI
850 {
851 PreferenceSystem* m_preferencesystem;
852 public:
853 typedef PreferenceSystem Type;
854 STRING_CONSTANT( Name, "*" );
855
856 PreferenceSystemAPI(){
857         m_preferencesystem = &GetPreferenceSystem();
858 }
859 PreferenceSystem* getTable(){
860         return m_preferencesystem;
861 }
862 };
863
864 #include "modulesystem/singletonmodule.h"
865 #include "modulesystem/moduleregistry.h"
866
867 typedef SingletonModule<PreferenceSystemAPI> PreferenceSystemModule;
868 typedef Static<PreferenceSystemModule> StaticPreferenceSystemModule;
869 StaticRegisterModule staticRegisterPreferenceSystem( StaticPreferenceSystemModule::instance() );
870
871 void Preferences_Load(){
872         g_GamesDialog.LoadPrefs();
873
874         globalOutputStream() << "loading local preferences from " << g_Preferences.m_inipath->str << "\n";
875
876         if ( !Preferences_Load( g_preferences, g_Preferences.m_inipath->str, g_GamesDialog.m_sGameFile.c_str() ) ) {
877                 globalOutputStream() << "failed to load local preferences from " << g_Preferences.m_inipath->str << "\n";
878         }
879 }
880
881 void Preferences_Save(){
882         if ( g_preferences_globals.disable_ini ) {
883                 return;
884         }
885
886         g_GamesDialog.SavePrefs();
887
888         globalOutputStream() << "saving local preferences to " << g_Preferences.m_inipath->str << "\n";
889
890         if ( !Preferences_Save_Safe( g_preferences, g_Preferences.m_inipath->str ) ) {
891                 globalOutputStream() << "failed to save local preferences to " << g_Preferences.m_inipath->str << "\n";
892         }
893 }
894
895 void Preferences_Reset(){
896         file_remove( g_Preferences.m_inipath->str );
897 }
898
899
900 void PrefsDlg::PostModal( EMessageBoxReturn code ){
901         if ( code == eIDOK ) {
902                 Preferences_Save();
903                 UpdateAllWindows();
904         }
905 }
906
907 std::vector<const char*> g_restart_required;
908
909 void PreferencesDialog_restartRequired( const char* staticName ){
910         g_restart_required.push_back( staticName );
911 }
912
913 void PreferencesDialog_showDialog(){
914         if ( ConfirmModified( "Edit Preferences" ) && g_Preferences.DoModal() == eIDOK ) {
915                 if ( !g_restart_required.empty() ) {
916                         StringOutputStream message( 256 );
917                         message << "Preference changes require a restart:\n";
918                         for ( std::vector<const char*>::iterator i = g_restart_required.begin(); i != g_restart_required.end(); ++i )
919                         {
920                                 message << ( *i ) << '\n';
921                         }
922                         MainFrame_getWindow().alert( message.c_str() );
923                         g_restart_required.clear();
924                 }
925         }
926 }
927
928
929
930
931
932 void GameName_importString( const char* value ){
933         gamename_set( value );
934 }
935 typedef FreeCaller1<const char*, GameName_importString> GameNameImportStringCaller;
936 void GameName_exportString( const StringImportCallback& importer ){
937         importer( gamename_get() );
938 }
939 typedef FreeCaller1<const StringImportCallback&, GameName_exportString> GameNameExportStringCaller;
940
941 void GameMode_importString( const char* value ){
942         gamemode_set( value );
943 }
944 typedef FreeCaller1<const char*, GameMode_importString> GameModeImportStringCaller;
945 void GameMode_exportString( const StringImportCallback& importer ){
946         importer( gamemode_get() );
947 }
948 typedef FreeCaller1<const StringImportCallback&, GameMode_exportString> GameModeExportStringCaller;
949
950
951 void RegisterPreferences( PreferenceSystem& preferences ){
952 #ifdef WIN32
953         preferences.registerPreference( "UseCustomShaderEditor", BoolImportStringCaller( g_TextEditor_useWin32Editor ), BoolExportStringCaller( g_TextEditor_useWin32Editor ) );
954 #else
955         preferences.registerPreference( "UseCustomShaderEditor", BoolImportStringCaller( g_TextEditor_useCustomEditor ), BoolExportStringCaller( g_TextEditor_useCustomEditor ) );
956         preferences.registerPreference( "CustomShaderEditorCommand", CopiedStringImportStringCaller( g_TextEditor_editorCommand ), CopiedStringExportStringCaller( g_TextEditor_editorCommand ) );
957 #endif
958
959         preferences.registerPreference( "GameName", GameNameImportStringCaller(), GameNameExportStringCaller() );
960         preferences.registerPreference( "GameMode", GameModeImportStringCaller(), GameModeExportStringCaller() );
961 }
962
963 void Preferences_Init(){
964         RegisterPreferences( GetPreferenceSystem() );
965 }