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