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