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