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