]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/preferences.cpp
Fix menu and about url
[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
488 CGameDialog g_GamesDialog;
489
490
491 // =============================================================================
492 // Widget callbacks for PrefsDlg
493
494 static void OnButtonClean( GtkWidget *widget, gpointer data ){
495         // make sure this is what the user wants
496         if ( gtk_MessageBox( GTK_WIDGET( g_Preferences.GetWidget() ), "This will close Radiant and clean the corresponding registry entries.\n"
497                                                                                                                                   "Next time you start Radiant it will be good as new. Do you wish to continue?",
498                                                  "Reset Registry", eMB_YESNO, eMB_ICONASTERISK ) == eIDYES ) {
499                 PrefsDlg *dlg = (PrefsDlg*)data;
500                 dlg->EndModal( eIDCANCEL );
501
502                 g_preferences_globals.disable_ini = true;
503                 Preferences_Reset();
504                 gtk_main_quit();
505         }
506 }
507
508 // =============================================================================
509 // PrefsDlg class
510
511 /*
512    ========
513
514    very first prefs init deals with selecting the game and the game tools path
515    then we can load .ini stuff
516
517    using prefs / ini settings:
518    those are per-game
519
520    look in ~/.radiant/<version>/gamename
521    ========
522  */
523
524 #define PREFS_LOCAL_FILENAME "local.pref"
525
526 void PrefsDlg::Init(){
527         // m_global_rc_path has been set above
528         // m_rc_path is for game specific preferences
529         // takes the form: global-pref-path/gamename/prefs-file
530
531         // this is common to win32 and Linux init now
532         m_rc_path = g_string_new( m_global_rc_path->str );
533
534         // game sub-dir
535         g_string_append( m_rc_path, g_pGameDescription->mGameFile.c_str() );
536         g_string_append( m_rc_path, "/" );
537         Q_mkdir( m_rc_path->str );
538
539         // then the ini file
540         m_inipath = g_string_new( m_rc_path->str );
541         g_string_append( m_inipath, PREFS_LOCAL_FILENAME );
542 }
543
544 void notebook_set_page( GtkWidget* notebook, GtkWidget* page ){
545         int pagenum = gtk_notebook_page_num( GTK_NOTEBOOK( notebook ), page );
546         if ( gtk_notebook_get_current_page( GTK_NOTEBOOK( notebook ) ) != pagenum ) {
547                 gtk_notebook_set_current_page( GTK_NOTEBOOK( notebook ), pagenum );
548         }
549 }
550
551 void PrefsDlg::showPrefPage( GtkWidget* prefpage ){
552         notebook_set_page( m_notebook, prefpage );
553         return;
554 }
555
556 static void treeSelection( GtkTreeSelection* selection, gpointer data ){
557         PrefsDlg *dlg = (PrefsDlg*)data;
558
559         GtkTreeModel* model;
560         GtkTreeIter selected;
561         if ( gtk_tree_selection_get_selected( selection, &model, &selected ) ) {
562                 GtkWidget* prefpage;
563                 gtk_tree_model_get( model, &selected, 1, (gpointer*)&prefpage, -1 );
564                 dlg->showPrefPage( prefpage );
565         }
566 }
567
568 typedef std::list<PreferenceGroupCallback> PreferenceGroupCallbacks;
569
570 inline void PreferenceGroupCallbacks_constructGroup( const PreferenceGroupCallbacks& callbacks, PreferenceGroup& group ){
571         for ( PreferenceGroupCallbacks::const_iterator i = callbacks.begin(); i != callbacks.end(); ++i )
572         {
573                 ( *i )( group );
574         }
575 }
576
577
578 inline void PreferenceGroupCallbacks_pushBack( PreferenceGroupCallbacks& callbacks, const PreferenceGroupCallback& callback ){
579         callbacks.push_back( callback );
580 }
581
582 typedef std::list<PreferencesPageCallback> PreferencesPageCallbacks;
583
584 inline void PreferencesPageCallbacks_constructPage( const PreferencesPageCallbacks& callbacks, PreferencesPage& page ){
585         for ( PreferencesPageCallbacks::const_iterator i = callbacks.begin(); i != callbacks.end(); ++i )
586         {
587                 ( *i )( page );
588         }
589 }
590
591 inline void PreferencesPageCallbacks_pushBack( PreferencesPageCallbacks& callbacks, const PreferencesPageCallback& callback ){
592         callbacks.push_back( callback );
593 }
594
595 PreferencesPageCallbacks g_interfacePreferences;
596 void PreferencesDialog_addInterfacePreferences( const PreferencesPageCallback& callback ){
597         PreferencesPageCallbacks_pushBack( g_interfacePreferences, callback );
598 }
599 PreferenceGroupCallbacks g_interfaceCallbacks;
600 void PreferencesDialog_addInterfacePage( const PreferenceGroupCallback& callback ){
601         PreferenceGroupCallbacks_pushBack( g_interfaceCallbacks, callback );
602 }
603
604 PreferencesPageCallbacks g_displayPreferences;
605 void PreferencesDialog_addDisplayPreferences( const PreferencesPageCallback& callback ){
606         PreferencesPageCallbacks_pushBack( g_displayPreferences, callback );
607 }
608 PreferenceGroupCallbacks g_displayCallbacks;
609 void PreferencesDialog_addDisplayPage( const PreferenceGroupCallback& callback ){
610         PreferenceGroupCallbacks_pushBack( g_displayCallbacks, callback );
611 }
612
613 PreferencesPageCallbacks g_settingsPreferences;
614 void PreferencesDialog_addSettingsPreferences( const PreferencesPageCallback& callback ){
615         PreferencesPageCallbacks_pushBack( g_settingsPreferences, callback );
616 }
617 PreferenceGroupCallbacks g_settingsCallbacks;
618 void PreferencesDialog_addSettingsPage( const PreferenceGroupCallback& callback ){
619         PreferenceGroupCallbacks_pushBack( g_settingsCallbacks, callback );
620 }
621
622 void Widget_updateDependency( GtkWidget* self, GtkWidget* toggleButton ){
623         gtk_widget_set_sensitive( self, gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( toggleButton ) ) && GTK_WIDGET_IS_SENSITIVE( toggleButton ) );
624 }
625
626 void ToggleButton_toggled_Widget_updateDependency( GtkWidget *toggleButton, GtkWidget* self ){
627         Widget_updateDependency( self, toggleButton );
628 }
629
630 void ToggleButton_state_changed_Widget_updateDependency( GtkWidget* toggleButton, GtkStateType state, GtkWidget* self ){
631         if ( state == GTK_STATE_INSENSITIVE ) {
632                 Widget_updateDependency( self, toggleButton );
633         }
634 }
635
636 void Widget_connectToggleDependency( GtkWidget* self, GtkWidget* toggleButton ){
637         g_signal_connect( G_OBJECT( toggleButton ), "state_changed", G_CALLBACK( ToggleButton_state_changed_Widget_updateDependency ), self );
638         g_signal_connect( G_OBJECT( toggleButton ), "toggled", G_CALLBACK( ToggleButton_toggled_Widget_updateDependency ), self );
639         Widget_updateDependency( self, toggleButton );
640 }
641
642
643 inline GtkWidget* getVBox( GtkWidget* page ){
644         return gtk_bin_get_child( GTK_BIN( page ) );
645 }
646
647 GtkTreeIter PreferenceTree_appendPage( GtkTreeStore* store, GtkTreeIter* parent, const char* name, GtkWidget* page ){
648         GtkTreeIter group;
649         gtk_tree_store_append( store, &group, parent );
650         gtk_tree_store_set( store, &group, 0, name, 1, page, -1 );
651         return group;
652 }
653
654 GtkWidget* PreferencePages_addPage( GtkWidget* notebook, const char* name ){
655         GtkWidget* preflabel = gtk_label_new( name );
656         gtk_widget_show( preflabel );
657
658         GtkWidget* pageframe = gtk_frame_new( name );
659         gtk_container_set_border_width( GTK_CONTAINER( pageframe ), 4 );
660         gtk_widget_show( pageframe );
661
662         GtkWidget* vbox = gtk_vbox_new( FALSE, 4 );
663         gtk_widget_show( vbox );
664         gtk_container_set_border_width( GTK_CONTAINER( vbox ), 4 );
665         gtk_container_add( GTK_CONTAINER( pageframe ), vbox );
666
667         // Add the page to the notebook
668         gtk_notebook_append_page( GTK_NOTEBOOK( notebook ), pageframe, preflabel );
669
670         return pageframe;
671 }
672
673 class PreferenceTreeGroup : public PreferenceGroup
674 {
675 Dialog& m_dialog;
676 GtkWidget* m_notebook;
677 GtkTreeStore* m_store;
678 GtkTreeIter m_group;
679 public:
680 PreferenceTreeGroup( Dialog& dialog, GtkWidget* notebook, GtkTreeStore* store, GtkTreeIter group ) :
681         m_dialog( dialog ),
682         m_notebook( notebook ),
683         m_store( store ),
684         m_group( group ){
685 }
686 PreferencesPage createPage( const char* treeName, const char* frameName ){
687         GtkWidget* page = PreferencePages_addPage( m_notebook, frameName );
688         PreferenceTree_appendPage( m_store, &m_group, treeName, page );
689         return PreferencesPage( m_dialog, getVBox( page ) );
690 }
691 };
692
693 GtkWindow* PrefsDlg::BuildDialog(){
694         PreferencesDialog_addInterfacePreferences( FreeCaller1<PreferencesPage&, Interface_constructPreferences>() );
695         Mouse_registerPreferencesPage();
696
697         GtkWindow* dialog = create_floating_window( "NetRadiant Preferences", m_parent );
698
699         {
700                 GtkWidget* mainvbox = gtk_vbox_new( FALSE, 5 );
701                 gtk_container_add( GTK_CONTAINER( dialog ), mainvbox );
702                 gtk_container_set_border_width( GTK_CONTAINER( mainvbox ), 5 );
703                 gtk_widget_show( mainvbox );
704
705                 {
706                         GtkWidget* hbox = gtk_hbox_new( FALSE, 5 );
707                         gtk_widget_show( hbox );
708                         gtk_box_pack_end( GTK_BOX( mainvbox ), hbox, FALSE, TRUE, 0 );
709
710                         {
711                                 GtkButton* button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &m_modal );
712                                 gtk_box_pack_end( GTK_BOX( hbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
713                         }
714                         {
715                                 GtkButton* button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &m_modal );
716                                 gtk_box_pack_end( GTK_BOX( hbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
717                         }
718                         {
719                                 GtkButton* button = create_dialog_button( "Clean", G_CALLBACK( OnButtonClean ), this );
720                                 gtk_box_pack_end( GTK_BOX( hbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
721                         }
722                 }
723
724                 {
725                         GtkWidget* hbox = gtk_hbox_new( FALSE, 5 );
726                         gtk_box_pack_start( GTK_BOX( mainvbox ), hbox, TRUE, TRUE, 0 );
727                         gtk_widget_show( hbox );
728
729                         {
730                                 GtkWidget* sc_win = gtk_scrolled_window_new( 0, 0 );
731                                 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( sc_win ), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC );
732                                 gtk_box_pack_start( GTK_BOX( hbox ), sc_win, FALSE, FALSE, 0 );
733                                 gtk_widget_show( sc_win );
734                                 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( sc_win ), GTK_SHADOW_IN );
735
736                                 // prefs pages notebook
737                                 m_notebook = gtk_notebook_new();
738                                 // hide the notebook tabs since its not supposed to look like a notebook
739                                 gtk_notebook_set_show_tabs( GTK_NOTEBOOK( m_notebook ), FALSE );
740                                 gtk_box_pack_start( GTK_BOX( hbox ), m_notebook, TRUE, TRUE, 0 );
741                                 gtk_widget_show( m_notebook );
742
743
744                                 {
745                                         GtkTreeStore* store = gtk_tree_store_new( 2, G_TYPE_STRING, G_TYPE_POINTER );
746
747                                         GtkWidget* view = gtk_tree_view_new_with_model( GTK_TREE_MODEL( store ) );
748                                         gtk_tree_view_set_headers_visible( GTK_TREE_VIEW( view ), FALSE );
749
750                                         {
751                                                 GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
752                                                 GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes( "Preferences", renderer, "text", 0, NULL );
753                                                 gtk_tree_view_append_column( GTK_TREE_VIEW( view ), column );
754                                         }
755
756                                         {
757                                                 GtkTreeSelection* selection = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
758                                                 g_signal_connect( G_OBJECT( selection ), "changed", G_CALLBACK( treeSelection ), this );
759                                         }
760
761                                         gtk_widget_show( view );
762
763                                         gtk_container_add( GTK_CONTAINER( sc_win ), view );
764
765                                         {
766                                                 /********************************************************************/
767                                                 /* Add preference tree options                                      */
768                                                 /********************************************************************/
769                                                 // Front page...
770                                                 //GtkWidget* front =
771                                                 PreferencePages_addPage( m_notebook, "Front Page" );
772
773                                                 {
774                                                         GtkWidget* global = PreferencePages_addPage( m_notebook, "Global Preferences" );
775                                                         {
776                                                                 PreferencesPage preferencesPage( *this, getVBox( global ) );
777                                                                 Global_constructPreferences( preferencesPage );
778                                                         }
779                                                         GtkTreeIter group = PreferenceTree_appendPage( store, 0, "Global", global );
780                                                         {
781                                                                 GtkWidget* game = PreferencePages_addPage( m_notebook, "Game" );
782                                                                 PreferencesPage preferencesPage( *this, getVBox( game ) );
783                                                                 g_GamesDialog.CreateGlobalFrame( preferencesPage );
784
785                                                                 PreferenceTree_appendPage( store, &group, "Game", game );
786                                                         }
787                                                 }
788
789                                                 {
790                                                         GtkWidget* interfacePage = PreferencePages_addPage( m_notebook, "Interface Preferences" );
791                                                         {
792                                                                 PreferencesPage preferencesPage( *this, getVBox( interfacePage ) );
793                                                                 PreferencesPageCallbacks_constructPage( g_interfacePreferences, preferencesPage );
794                                                         }
795
796                                                         GtkTreeIter group = PreferenceTree_appendPage( store, 0, "Interface", interfacePage );
797                                                         PreferenceTreeGroup preferenceGroup( *this, m_notebook, store, group );
798
799                                                         PreferenceGroupCallbacks_constructGroup( g_interfaceCallbacks, preferenceGroup );
800                                                 }
801
802                                                 {
803                                                         GtkWidget* display = PreferencePages_addPage( m_notebook, "Display Preferences" );
804                                                         {
805                                                                 PreferencesPage preferencesPage( *this, getVBox( display ) );
806                                                                 PreferencesPageCallbacks_constructPage( g_displayPreferences, preferencesPage );
807                                                         }
808                                                         GtkTreeIter group = PreferenceTree_appendPage( store, 0, "Display", display );
809                                                         PreferenceTreeGroup preferenceGroup( *this, m_notebook, store, group );
810
811                                                         PreferenceGroupCallbacks_constructGroup( g_displayCallbacks, preferenceGroup );
812                                                 }
813
814                                                 {
815                                                         GtkWidget* settings = PreferencePages_addPage( m_notebook, "General Settings" );
816                                                         {
817                                                                 PreferencesPage preferencesPage( *this, getVBox( settings ) );
818                                                                 PreferencesPageCallbacks_constructPage( g_settingsPreferences, preferencesPage );
819                                                         }
820
821                                                         GtkTreeIter group = PreferenceTree_appendPage( store, 0, "Settings", settings );
822                                                         PreferenceTreeGroup preferenceGroup( *this, m_notebook, store, group );
823
824                                                         PreferenceGroupCallbacks_constructGroup( g_settingsCallbacks, preferenceGroup );
825                                                 }
826                                         }
827
828                                         gtk_tree_view_expand_all( GTK_TREE_VIEW( view ) );
829
830                                         g_object_unref( G_OBJECT( store ) );
831                                 }
832                         }
833                 }
834         }
835
836         gtk_notebook_set_page( GTK_NOTEBOOK( m_notebook ), 0 );
837
838         return dialog;
839 }
840
841 preferences_globals_t g_preferences_globals;
842
843 PrefsDlg g_Preferences;               // global prefs instance
844
845
846 void PreferencesDialog_constructWindow( GtkWindow* main_window ){
847         g_Preferences.m_parent = main_window;
848         g_Preferences.Create();
849 }
850 void PreferencesDialog_destroyWindow(){
851         g_Preferences.Destroy();
852 }
853
854
855 PreferenceDictionary g_preferences;
856
857 PreferenceSystem& GetPreferenceSystem(){
858         return g_preferences;
859 }
860
861 class PreferenceSystemAPI
862 {
863 PreferenceSystem* m_preferencesystem;
864 public:
865 typedef PreferenceSystem Type;
866 STRING_CONSTANT( Name, "*" );
867
868 PreferenceSystemAPI(){
869         m_preferencesystem = &GetPreferenceSystem();
870 }
871 PreferenceSystem* getTable(){
872         return m_preferencesystem;
873 }
874 };
875
876 #include "modulesystem/singletonmodule.h"
877 #include "modulesystem/moduleregistry.h"
878
879 typedef SingletonModule<PreferenceSystemAPI> PreferenceSystemModule;
880 typedef Static<PreferenceSystemModule> StaticPreferenceSystemModule;
881 StaticRegisterModule staticRegisterPreferenceSystem( StaticPreferenceSystemModule::instance() );
882
883 void Preferences_Load(){
884         g_GamesDialog.LoadPrefs();
885
886         globalOutputStream() << "loading local preferences from " << g_Preferences.m_inipath->str << "\n";
887
888         if ( !Preferences_Load( g_preferences, g_Preferences.m_inipath->str, g_GamesDialog.m_sGameFile.c_str() ) ) {
889                 globalOutputStream() << "failed to load local preferences from " << g_Preferences.m_inipath->str << "\n";
890         }
891 }
892
893 void Preferences_Save(){
894         if ( g_preferences_globals.disable_ini ) {
895                 return;
896         }
897
898         g_GamesDialog.SavePrefs();
899
900         globalOutputStream() << "saving local preferences to " << g_Preferences.m_inipath->str << "\n";
901
902         if ( !Preferences_Save_Safe( g_preferences, g_Preferences.m_inipath->str ) ) {
903                 globalOutputStream() << "failed to save local preferences to " << g_Preferences.m_inipath->str << "\n";
904         }
905 }
906
907 void Preferences_Reset(){
908         file_remove( g_Preferences.m_inipath->str );
909 }
910
911
912 void PrefsDlg::PostModal( EMessageBoxReturn code ){
913         if ( code == eIDOK ) {
914                 Preferences_Save();
915                 UpdateAllWindows();
916         }
917 }
918
919 std::vector<const char*> g_restart_required;
920
921 void PreferencesDialog_restartRequired( const char* staticName ){
922         g_restart_required.push_back( staticName );
923 }
924
925 void PreferencesDialog_showDialog(){
926         if ( ConfirmModified( "Edit Preferences" ) && g_Preferences.DoModal() == eIDOK ) {
927                 if ( !g_restart_required.empty() ) {
928                         StringOutputStream message( 256 );
929                         message << "Preference changes require a restart:\n";
930                         for ( std::vector<const char*>::iterator i = g_restart_required.begin(); i != g_restart_required.end(); ++i )
931                         {
932                                 message << ( *i ) << '\n';
933                         }
934                         gtk_MessageBox( GTK_WIDGET( MainFrame_getWindow() ), message.c_str() );
935                         g_restart_required.clear();
936                 }
937         }
938 }
939
940
941
942
943
944 void GameName_importString( const char* value ){
945         gamename_set( value );
946 }
947 typedef FreeCaller1<const char*, GameName_importString> GameNameImportStringCaller;
948 void GameName_exportString( const StringImportCallback& importer ){
949         importer( gamename_get() );
950 }
951 typedef FreeCaller1<const StringImportCallback&, GameName_exportString> GameNameExportStringCaller;
952
953 void GameMode_importString( const char* value ){
954         gamemode_set( value );
955 }
956 typedef FreeCaller1<const char*, GameMode_importString> GameModeImportStringCaller;
957 void GameMode_exportString( const StringImportCallback& importer ){
958         importer( gamemode_get() );
959 }
960 typedef FreeCaller1<const StringImportCallback&, GameMode_exportString> GameModeExportStringCaller;
961
962
963 void RegisterPreferences( PreferenceSystem& preferences ){
964 #ifdef WIN32
965         preferences.registerPreference( "UseCustomShaderEditor", BoolImportStringCaller( g_TextEditor_useWin32Editor ), BoolExportStringCaller( g_TextEditor_useWin32Editor ) );
966 #else
967         preferences.registerPreference( "UseCustomShaderEditor", BoolImportStringCaller( g_TextEditor_useCustomEditor ), BoolExportStringCaller( g_TextEditor_useCustomEditor ) );
968         preferences.registerPreference( "CustomShaderEditorCommand", CopiedStringImportStringCaller( g_TextEditor_editorCommand ), CopiedStringExportStringCaller( g_TextEditor_editorCommand ) );
969 #endif
970
971         preferences.registerPreference( "GameName", GameNameImportStringCaller(), GameNameExportStringCaller() );
972         preferences.registerPreference( "GameMode", GameModeImportStringCaller(), GameModeExportStringCaller() );
973 }
974
975 void Preferences_Init(){
976         RegisterPreferences( GetPreferenceSystem() );
977 }