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