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