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