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