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