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