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