]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/preferences.cpp
Merge branch 'NateEag-master-patch-12920' into 'master'
[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 << DataPath_get() << "gamepacks/" << 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         std::string tmpName( filename );
201         tmpName += "TMP";
202
203         return Preferences_Save( preferences, tmpName.c_str() )
204                    && ( !file_exists( filename ) || file_remove( filename ) )
205                    && file_move( tmpName.data(), filename );
206 }
207
208
209 struct LogConsole {
210         static void Export(const Callback<void(bool)> &returnz) {
211                 returnz(g_Console_enableLogging);
212         }
213
214         static void Import(bool value) {
215                 g_Console_enableLogging = value;
216                 Sys_EnableLogFile(g_Console_enableLogging);
217         }
218 };
219
220
221 void RegisterGlobalPreferences( PreferenceSystem& preferences ){
222         preferences.registerPreference( "gamefile", make_property_string( g_GamesDialog.m_sGameFile ) );
223         preferences.registerPreference( "gamePrompt", make_property_string( g_GamesDialog.m_bGamePrompt ) );
224         preferences.registerPreference( "skipGamePromptOnce", make_property_string( g_GamesDialog.m_bSkipGamePromptOnce ) );
225         preferences.registerPreference( "log console", make_property_string<LogConsole>() );
226 }
227
228
229 PreferenceDictionary g_global_preferences;
230
231 void GlobalPreferences_Init(){
232         RegisterGlobalPreferences( g_global_preferences );
233 }
234
235 void CGameDialog::LoadPrefs(){
236         // load global .pref file
237         StringOutputStream strGlobalPref( 256 );
238         strGlobalPref << g_Preferences.m_global_rc_path->str << "global.pref";
239
240         globalOutputStream() << "loading global preferences from " << makeQuoted( strGlobalPref.c_str() ) << "\n";
241
242         if ( !Preferences_Load( g_global_preferences, strGlobalPref.c_str(), "global" ) ) {
243                 globalOutputStream() << "failed to load global preferences from " << strGlobalPref.c_str() << "\n";
244         }
245 }
246
247 void CGameDialog::SavePrefs(){
248         StringOutputStream strGlobalPref( 256 );
249         strGlobalPref << g_Preferences.m_global_rc_path->str << "global.pref";
250
251         globalOutputStream() << "saving global preferences to " << strGlobalPref.c_str() << "\n";
252
253         if ( !Preferences_Save_Safe( g_global_preferences, strGlobalPref.c_str() ) ) {
254                 globalOutputStream() << "failed to save global preferences to " << strGlobalPref.c_str() << "\n";
255         }
256 }
257
258 void CGameDialog::DoGameDialog(){
259         // show the UI
260         DoModal();
261
262         // we save the prefs file
263         SavePrefs();
264 }
265
266 void CGameDialog::GameFileImport( int value ){
267         m_nComboSelect = value;
268         // use value to set m_sGameFile
269         std::list<CGameDescription *>::iterator iGame = mGames.begin();
270         int i;
271         for ( i = 0; i < value; i++ )
272         {
273                 ++iGame;
274         }
275
276         if ( ( *iGame )->mGameFile != m_sGameFile ) {
277         m_sGameFile = ( *iGame )->mGameFile;
278
279                 // do not trigger radiant restart when switching game on startup using Global Preferences dialog
280                 if ( !onStartup ) {
281                         PreferencesDialog_restartRequired( "Selected Game" );
282                 }
283         }
284
285         // onStartup can only be true once, when Global Preferences are displayed at startup
286         onStartup = false;
287 }
288
289 void CGameDialog::GameFileExport( const Callback<void(int)> & importCallback ) const {
290         // use m_sGameFile to set value
291         std::list<CGameDescription *>::const_iterator iGame;
292         int i = 0;
293         for ( iGame = mGames.begin(); iGame != mGames.end(); ++iGame )
294         {
295                 if ( ( *iGame )->mGameFile == m_sGameFile ) {
296                         m_nComboSelect = i;
297                         break;
298                 }
299                 i++;
300         }
301         importCallback( m_nComboSelect );
302 }
303
304 struct CGameDialog_GameFile {
305         static void Export(const CGameDialog &self, const Callback<void(int)> &returnz) {
306                 self.GameFileExport(returnz);
307         }
308
309         static void Import(CGameDialog &self, int value) {
310                 self.GameFileImport(value);
311         }
312 };
313
314 void CGameDialog::CreateGlobalFrame( PreferencesPage& page ){
315         std::vector<const char*> games;
316         games.reserve( mGames.size() );
317         for ( std::list<CGameDescription *>::iterator i = mGames.begin(); i != mGames.end(); ++i )
318         {
319                 games.push_back( ( *i )->getRequiredKeyValue( "name" ) );
320         }
321         page.appendCombo(
322                 "Select the game",
323                 StringArrayRange( &( *games.begin() ), &( *games.end() ) ),
324                 make_property<CGameDialog_GameFile>(*this)
325                 );
326         page.appendCheckBox( "Startup", "Show Global Preferences", m_bGamePrompt );
327 }
328
329 ui::Window CGameDialog::BuildDialog(){
330         auto frame = create_dialog_frame( "Game settings", ui::Shadow::ETCHED_IN );
331
332         auto vbox2 = create_dialog_vbox( 0, 4 );
333         frame.add(vbox2);
334
335         {
336                 PreferencesPage preferencesPage( *this, vbox2 );
337                 Global_constructPreferences( preferencesPage );
338                 CreateGlobalFrame( preferencesPage );
339         }
340
341         return create_simple_modal_dialog_window( "Global Preferences", m_modal, frame );
342 }
343
344 static void StringReplace( std::string& input, const std::string& first, const std::string& second )
345 {
346         size_t found = 0;
347         while ( ( found = input.find(first, found) ) != std::string::npos )
348         {
349                 input.replace( found, first.length(), second );
350         }
351 }
352
353 // FIXME, for some unknown reason it sorts “Quake 3” after “Quake 4”.
354 static bool CompareGameName( CGameDescription *first, CGameDescription *second )
355 {
356         std::string string1( first->getRequiredKeyValue( "name" ) );
357         std::string string2( second->getRequiredKeyValue( "name" ) );
358
359         // HACK: Replace some roman numerals.
360         StringReplace( string1, " III", " 3" );
361         StringReplace( string2, " III", " 3" );
362         StringReplace( string1, " II", " 2" );
363         StringReplace( string2, " II", " 2" );
364
365         return string1 < string2;
366 }
367
368 void CGameDialog::ScanForGames(){
369         StringOutputStream strGamesPath( 256 );
370         strGamesPath << DataPath_get() << "gamepacks/games/";
371         const char *path = strGamesPath.c_str();
372
373         globalOutputStream() << "Scanning for game description files: " << path << '\n';
374
375         /*!
376            \todo FIXME LINUX:
377            do we put game description files below AppPath, or in ~/.radiant
378            i.e. read only or read/write?
379            my guess .. readonly cause it's an install
380            we will probably want to add ~/.radiant/<version>/games/ scanning on top of that for developers
381            (if that's really needed)
382          */
383
384         Directory_forEach(path, [&](const char *name) {
385                 if (!extension_equal(path_get_extension(name), "game")) {
386                         return;
387                 }
388                 StringOutputStream strPath(256);
389                 strPath << path << name;
390                 globalOutputStream() << strPath.c_str() << '\n';
391
392                 xmlDocPtr pDoc = xmlParseFile(strPath.c_str());
393                 if (pDoc) {
394                         mGames.push_front(new CGameDescription(pDoc, name));
395                         xmlFreeDoc(pDoc);
396                 } else {
397                         globalErrorStream() << "XML parser failed on '" << strPath.c_str() << "'\n";
398                 }
399
400                 mGames.sort(CompareGameName);
401         });
402 }
403
404 CGameDescription* CGameDialog::GameDescriptionForComboItem(){
405         std::list<CGameDescription *>::iterator iGame;
406         int i = 0;
407         for ( iGame = mGames.begin(); iGame != mGames.end(); ++iGame,i++ )
408         {
409                 if ( i == m_nComboSelect ) {
410                         return ( *iGame );
411                 }
412         }
413         return 0; // not found
414 }
415
416 void CGameDialog::InitGlobalPrefPath(){
417         g_Preferences.m_global_rc_path = g_string_new( SettingsPath_get() );
418 }
419
420 void CGameDialog::Reset(){
421         if ( !g_Preferences.m_global_rc_path ) {
422                 InitGlobalPrefPath();
423         }
424         StringOutputStream strGlobalPref( 256 );
425         strGlobalPref << g_Preferences.m_global_rc_path->str << "global.pref";
426         file_remove( strGlobalPref.c_str() );
427 }
428
429 void CGameDialog::Init(){
430         bool gamePrompt = false;
431
432         InitGlobalPrefPath();
433         LoadPrefs();
434         ScanForGames();
435
436         if ( mGames.empty() ) {
437                 Error( "Didn't find any valid game file descriptions, aborting\n" );
438         }
439         else
440         {
441                 std::list<CGameDescription *>::iterator iGame, iPrevGame;
442                 for ( iGame = mGames.begin(), iPrevGame = mGames.end(); iGame != mGames.end(); iPrevGame = iGame, ++iGame )
443                 {
444                         if ( iPrevGame != mGames.end() ) {
445                                 if ( strcmp( ( *iGame )->getRequiredKeyValue( "name" ), ( *iPrevGame )->getRequiredKeyValue( "name" ) ) < 0 ) {
446                                         CGameDescription *h = *iGame;
447                                         *iGame = *iPrevGame;
448                                         *iPrevGame = h;
449                                 }
450                         }
451                 }
452         }
453
454         CGameDescription* currentGameDescription = 0;
455
456         // m_bSkipGamePromptOnce is used to not prompt for game on restart, only on fresh startup
457         if ( m_bGamePrompt && !m_bSkipGamePromptOnce ) {
458                 gamePrompt = true;
459         }
460
461         m_bSkipGamePromptOnce = false;
462         g_GamesDialog.SavePrefs();
463
464         if ( !gamePrompt ) {
465                 // search by .game name
466                 std::list<CGameDescription *>::iterator iGame;
467                 for ( iGame = mGames.begin(); iGame != mGames.end(); ++iGame )
468                 {
469                         if ( ( *iGame )->mGameFile == m_sGameFile ) {
470                                 currentGameDescription = ( *iGame );
471                                 break;
472                         }
473                 }
474         }
475
476         if ( gamePrompt || !currentGameDescription ) {
477                 onStartup = true;
478                 Create();
479                 DoGameDialog();
480                 // use m_nComboSelect to identify the game to run as and set the globals
481                 currentGameDescription = GameDescriptionForComboItem();
482                 ASSERT_NOTNULL( currentGameDescription );
483         }
484         else {
485                 onStartup = false;
486         }
487
488         g_pGameDescription = currentGameDescription;
489
490         g_pGameDescription->Dump();
491 }
492
493 CGameDialog::~CGameDialog(){
494         // free all the game descriptions
495         std::list<CGameDescription *>::iterator iGame;
496         for ( iGame = mGames.begin(); iGame != mGames.end(); ++iGame )
497         {
498                 delete ( *iGame );
499                 *iGame = 0;
500         }
501         if ( GetWidget() ) {
502                 Destroy();
503         }
504 }
505
506 inline const char* GameDescription_getIdentifier( const CGameDescription& gameDescription ){
507         const char* identifier = gameDescription.getKeyValue( "index" );
508         if ( string_empty( identifier ) ) {
509                 identifier = "1";
510         }
511         return identifier;
512 }
513
514
515 CGameDialog g_GamesDialog;
516
517
518 // =============================================================================
519 // Widget callbacks for PrefsDlg
520
521 static void OnButtonClean( ui::Widget widget, gpointer data ){
522         // make sure this is what the user wants
523         if ( ui::alert( g_Preferences.GetWidget(), "This will close " RADIANT_NAME " and clean the corresponding registry entries.\n"
524                                                                                                                                   "Next time you start " RADIANT_NAME " it will be good as new. Do you wish to continue?",
525                                                  "Reset Registry", ui::alert_type::YESNO, ui::alert_icon::Asterisk ) == ui::alert_response::YES ) {
526                 PrefsDlg *dlg = (PrefsDlg*)data;
527                 dlg->EndModal( eIDCANCEL );
528
529                 g_preferences_globals.disable_ini = true;
530                 Preferences_Reset();
531                 gtk_main_quit();
532         }
533 }
534
535 // =============================================================================
536 // PrefsDlg class
537
538 /*
539    ========
540
541    very first prefs init deals with selecting the game and the game tools path
542    then we can load .ini stuff
543
544    using prefs / ini settings:
545    those are per-game
546
547    look in ~/.radiant/<version>/gamename
548    ========
549  */
550
551 const char *PREFS_LOCAL_FILENAME = "local.pref";
552
553 void PrefsDlg::Init(){
554         // m_global_rc_path has been set above
555         // m_rc_path is for game specific preferences
556         // takes the form: global-pref-path/gamename/prefs-file
557
558         // this is common to win32 and Linux init now
559         m_rc_path = g_string_new( m_global_rc_path->str );
560
561         // game sub-dir
562         g_string_append( m_rc_path, g_pGameDescription->mGameFile.c_str() );
563         g_string_append( m_rc_path, "/" );
564         Q_mkdir( m_rc_path->str );
565
566         // then the ini file
567         m_inipath = g_string_new( m_rc_path->str );
568         g_string_append( m_inipath, PREFS_LOCAL_FILENAME );
569 }
570
571 void notebook_set_page( ui::Widget notebook, ui::Widget page ){
572         int pagenum = gtk_notebook_page_num( GTK_NOTEBOOK( notebook ), page );
573         if ( gtk_notebook_get_current_page( GTK_NOTEBOOK( notebook ) ) != pagenum ) {
574                 gtk_notebook_set_current_page( GTK_NOTEBOOK( notebook ), pagenum );
575         }
576 }
577
578 void PrefsDlg::showPrefPage( ui::Widget prefpage ){
579         notebook_set_page( m_notebook, prefpage );
580         return;
581 }
582
583 static void treeSelection( ui::TreeSelection selection, gpointer data ){
584         PrefsDlg *dlg = (PrefsDlg*)data;
585
586         GtkTreeModel* model;
587         GtkTreeIter selected;
588         if ( gtk_tree_selection_get_selected( selection, &model, &selected ) ) {
589                 ui::Widget prefpage{ui::null};
590                 gtk_tree_model_get( model, &selected, 1, (gpointer*)&prefpage, -1 );
591                 dlg->showPrefPage( prefpage );
592         }
593 }
594
595 typedef std::list<PreferenceGroupCallback> PreferenceGroupCallbacks;
596
597 inline void PreferenceGroupCallbacks_constructGroup( const PreferenceGroupCallbacks& callbacks, PreferenceGroup& group ){
598         for ( PreferenceGroupCallbacks::const_iterator i = callbacks.begin(); i != callbacks.end(); ++i )
599         {
600                 ( *i )( group );
601         }
602 }
603
604
605 inline void PreferenceGroupCallbacks_pushBack( PreferenceGroupCallbacks& callbacks, const PreferenceGroupCallback& callback ){
606         callbacks.push_back( callback );
607 }
608
609 typedef std::list<PreferencesPageCallback> PreferencesPageCallbacks;
610
611 inline void PreferencesPageCallbacks_constructPage( const PreferencesPageCallbacks& callbacks, PreferencesPage& page ){
612         for ( PreferencesPageCallbacks::const_iterator i = callbacks.begin(); i != callbacks.end(); ++i )
613         {
614                 ( *i )( page );
615         }
616 }
617
618 inline void PreferencesPageCallbacks_pushBack( PreferencesPageCallbacks& callbacks, const PreferencesPageCallback& callback ){
619         callbacks.push_back( callback );
620 }
621
622 PreferencesPageCallbacks g_interfacePreferences;
623 void PreferencesDialog_addInterfacePreferences( const PreferencesPageCallback& callback ){
624         PreferencesPageCallbacks_pushBack( g_interfacePreferences, callback );
625 }
626 PreferenceGroupCallbacks g_interfaceCallbacks;
627 void PreferencesDialog_addInterfacePage( const PreferenceGroupCallback& callback ){
628         PreferenceGroupCallbacks_pushBack( g_interfaceCallbacks, callback );
629 }
630
631 PreferencesPageCallbacks g_displayPreferences;
632 void PreferencesDialog_addDisplayPreferences( const PreferencesPageCallback& callback ){
633         PreferencesPageCallbacks_pushBack( g_displayPreferences, callback );
634 }
635 PreferenceGroupCallbacks g_displayCallbacks;
636 void PreferencesDialog_addDisplayPage( const PreferenceGroupCallback& callback ){
637         PreferenceGroupCallbacks_pushBack( g_displayCallbacks, callback );
638 }
639
640 PreferencesPageCallbacks g_settingsPreferences;
641 void PreferencesDialog_addSettingsPreferences( const PreferencesPageCallback& callback ){
642         PreferencesPageCallbacks_pushBack( g_settingsPreferences, callback );
643 }
644 PreferenceGroupCallbacks g_settingsCallbacks;
645 void PreferencesDialog_addSettingsPage( const PreferenceGroupCallback& callback ){
646         PreferenceGroupCallbacks_pushBack( g_settingsCallbacks, callback );
647 }
648
649 void Widget_updateDependency( ui::Widget self, ui::Widget toggleButton ){
650         gtk_widget_set_sensitive( self, gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( toggleButton ) ) && gtk_widget_is_sensitive( toggleButton ) );
651 }
652
653 void ToggleButton_toggled_Widget_updateDependency( ui::Widget toggleButton, ui::Widget self ){
654         Widget_updateDependency( self, toggleButton );
655 }
656
657 void ToggleButton_state_changed_Widget_updateDependency( ui::Widget toggleButton, GtkStateType state, ui::Widget self ){
658         if ( state == GTK_STATE_INSENSITIVE ) {
659                 Widget_updateDependency( self, toggleButton );
660         }
661 }
662
663 void Widget_connectToggleDependency( ui::Widget self, ui::Widget toggleButton ){
664         toggleButton.connect( "state_changed", G_CALLBACK( ToggleButton_state_changed_Widget_updateDependency ), self );
665         toggleButton.connect( "toggled", G_CALLBACK( ToggleButton_toggled_Widget_updateDependency ), self );
666         Widget_updateDependency( self, toggleButton );
667 }
668
669
670 inline ui::VBox getVBox( ui::Bin page ){
671         return ui::VBox::from(gtk_bin_get_child(page));
672 }
673
674 GtkTreeIter PreferenceTree_appendPage( ui::TreeStore store, GtkTreeIter* parent, const char* name, ui::Widget page ){
675         GtkTreeIter group;
676         gtk_tree_store_append( store, &group, parent );
677         gtk_tree_store_set( store, &group, 0, name, 1, page, -1 );
678         return group;
679 }
680
681 ui::Bin PreferencePages_addPage( ui::Widget notebook, const char* name ){
682         ui::Widget preflabel = ui::Label( name );
683         preflabel.show();
684
685         auto pageframe = ui::Frame( name );
686         gtk_container_set_border_width( GTK_CONTAINER( pageframe ), 4 );
687         pageframe.show();
688
689         ui::Widget vbox = ui::VBox( FALSE, 4 );
690         vbox.show();
691         gtk_container_set_border_width( GTK_CONTAINER( vbox ), 4 );
692         pageframe.add(vbox);
693
694         // Add the page to the notebook
695         gtk_notebook_append_page( GTK_NOTEBOOK( notebook ), pageframe, preflabel );
696
697         return pageframe;
698 }
699
700 class PreferenceTreeGroup : public PreferenceGroup
701 {
702 Dialog& m_dialog;
703 ui::Widget m_notebook;
704 ui::TreeStore m_store;
705 GtkTreeIter m_group;
706 public:
707 PreferenceTreeGroup( Dialog& dialog, ui::Widget notebook, ui::TreeStore store, GtkTreeIter group ) :
708         m_dialog( dialog ),
709         m_notebook( notebook ),
710         m_store( store ),
711         m_group( group ){
712 }
713 PreferencesPage createPage( const char* treeName, const char* frameName ){
714         auto page = PreferencePages_addPage( m_notebook, frameName );
715         PreferenceTree_appendPage( m_store, &m_group, treeName, page );
716         return PreferencesPage( m_dialog, getVBox( page ) );
717 }
718 };
719
720 ui::Window PrefsDlg::BuildDialog(){
721         PreferencesDialog_addInterfacePreferences( makeCallbackF(Interface_constructPreferences) );
722         //Mouse_registerPreferencesPage();
723
724         ui::Window dialog = ui::Window(create_floating_window( RADIANT_NAME " Preferences", m_parent ));
725
726         gtk_window_set_transient_for( dialog, m_parent );
727         gtk_window_set_position( dialog, GTK_WIN_POS_CENTER_ON_PARENT );
728
729         {
730                 auto mainvbox = ui::VBox( FALSE, 5 );
731                 dialog.add(mainvbox);
732                 gtk_container_set_border_width( GTK_CONTAINER( mainvbox ), 5 );
733                 mainvbox.show();
734
735                 {
736                         auto hbox = ui::HBox( FALSE, 5 );
737                         hbox.show();
738                         mainvbox.pack_end(hbox, FALSE, TRUE, 0);
739
740                         {
741                                 auto button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &m_modal );
742                                 hbox.pack_end(button, FALSE, FALSE, 0);
743                         }
744                         {
745                                 auto button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &m_modal );
746                                 hbox.pack_end(button, FALSE, FALSE, 0);
747                         }
748                         {
749                                 auto button = create_dialog_button( "Clean", G_CALLBACK( OnButtonClean ), this );
750                                 hbox.pack_end(button, FALSE, FALSE, 0);
751                         }
752                 }
753
754                 {
755                         auto hbox = ui::HBox( FALSE, 5 );
756                         mainvbox.pack_start( hbox, TRUE, TRUE, 0 );
757                         hbox.show();
758
759                         {
760                                 auto sc_win = ui::ScrolledWindow(ui::New);
761                                 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( sc_win ), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC );
762                                 hbox.pack_start( sc_win, FALSE, FALSE, 0 );
763                                 sc_win.show();
764                                 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( sc_win ), GTK_SHADOW_IN );
765
766                                 // prefs pages notebook
767                                 m_notebook = ui::Widget::from(gtk_notebook_new());
768                                 // hide the notebook tabs since its not supposed to look like a notebook
769                                 gtk_notebook_set_show_tabs( GTK_NOTEBOOK( m_notebook ), FALSE );
770                                 hbox.pack_start( m_notebook, TRUE, TRUE, 0 );
771                                 m_notebook.show();
772
773
774                                 {
775                                         auto store = ui::TreeStore::from(gtk_tree_store_new( 2, G_TYPE_STRING, G_TYPE_POINTER ));
776
777                                         auto view = ui::TreeView(ui::TreeModel::from(store._handle));
778                                         gtk_tree_view_set_headers_visible(view, FALSE );
779
780                                         {
781                                                 auto renderer = ui::CellRendererText(ui::New);
782                         auto column = ui::TreeViewColumn( "Preferences", renderer, {{"text", 0}} );
783                                                 gtk_tree_view_append_column(view, column );
784                                         }
785
786                                         {
787                                                 auto selection = ui::TreeSelection::from(gtk_tree_view_get_selection(view));
788                                                 selection.connect( "changed", G_CALLBACK( treeSelection ), this );
789                                         }
790
791                                         view.show();
792
793                                         sc_win.add(view);
794
795                                         {
796                                                 /********************************************************************/
797                                                 /* Add preference tree options                                      */
798                                                 /********************************************************************/
799                                                 // Front page...
800                                                 //GtkWidget* front =
801                                                 PreferencePages_addPage( m_notebook, "Front Page" );
802
803                                                 {
804                                                         auto global = PreferencePages_addPage( m_notebook, "Global Preferences" );
805                                                         {
806                                                                 PreferencesPage preferencesPage( *this, getVBox( global ) );
807                                                                 Global_constructPreferences( preferencesPage );
808                                                         }
809                             auto group = PreferenceTree_appendPage( store, 0, "Global", global );
810                                                         {
811                                                                 auto game = PreferencePages_addPage( m_notebook, "Game" );
812                                                                 PreferencesPage preferencesPage( *this, getVBox( game ) );
813                                                                 g_GamesDialog.CreateGlobalFrame( preferencesPage );
814
815                                                                 PreferenceTree_appendPage( store, &group, "Game", game );
816                                                         }
817                                                 }
818
819                                                 {
820                                                         auto interfacePage = PreferencePages_addPage( m_notebook, "Interface Preferences" );
821                                                         {
822                                                                 PreferencesPage preferencesPage( *this, getVBox( interfacePage ) );
823                                                                 PreferencesPageCallbacks_constructPage( g_interfacePreferences, preferencesPage );
824                                                         }
825
826                             auto group = PreferenceTree_appendPage( store, 0, "Interface", interfacePage );
827                                                         PreferenceTreeGroup preferenceGroup( *this, m_notebook, store, group );
828
829                                                         PreferenceGroupCallbacks_constructGroup( g_interfaceCallbacks, preferenceGroup );
830                                                 }
831
832                                                 {
833                                                         auto display = PreferencePages_addPage( m_notebook, "Display Preferences" );
834                                                         {
835                                                                 PreferencesPage preferencesPage( *this, getVBox( display ) );
836                                                                 PreferencesPageCallbacks_constructPage( g_displayPreferences, preferencesPage );
837                                                         }
838                             auto group = PreferenceTree_appendPage( store, 0, "Display", display );
839                                                         PreferenceTreeGroup preferenceGroup( *this, m_notebook, store, group );
840
841                                                         PreferenceGroupCallbacks_constructGroup( g_displayCallbacks, preferenceGroup );
842                                                 }
843
844                                                 {
845                                                         auto settings = PreferencePages_addPage( m_notebook, "General Settings" );
846                                                         {
847                                                                 PreferencesPage preferencesPage( *this, getVBox( settings ) );
848                                                                 PreferencesPageCallbacks_constructPage( g_settingsPreferences, preferencesPage );
849                                                         }
850
851                             auto group = PreferenceTree_appendPage( store, 0, "Settings", settings );
852                                                         PreferenceTreeGroup preferenceGroup( *this, m_notebook, store, group );
853
854                                                         PreferenceGroupCallbacks_constructGroup( g_settingsCallbacks, preferenceGroup );
855                                                 }
856                                         }
857
858                                         gtk_tree_view_expand_all(view );
859
860                                         g_object_unref( G_OBJECT( store ) );
861                                 }
862                         }
863                 }
864         }
865
866         gtk_notebook_set_current_page( GTK_NOTEBOOK( m_notebook ), 0 );
867
868         return dialog;
869 }
870
871 preferences_globals_t g_preferences_globals;
872
873 PrefsDlg g_Preferences;               // global prefs instance
874
875
876 void PreferencesDialog_constructWindow( ui::Window main_window ){
877         g_Preferences.m_parent = main_window;
878         g_Preferences.Create();
879 }
880 void PreferencesDialog_destroyWindow(){
881         g_Preferences.Destroy();
882 }
883
884
885 PreferenceDictionary g_preferences;
886
887 PreferenceSystem& GetPreferenceSystem(){
888         return g_preferences;
889 }
890
891 class PreferenceSystemAPI
892 {
893 PreferenceSystem* m_preferencesystem;
894 public:
895 typedef PreferenceSystem Type;
896 STRING_CONSTANT( Name, "*" );
897
898 PreferenceSystemAPI(){
899         m_preferencesystem = &GetPreferenceSystem();
900 }
901 PreferenceSystem* getTable(){
902         return m_preferencesystem;
903 }
904 };
905
906 #include "modulesystem/singletonmodule.h"
907 #include "modulesystem/moduleregistry.h"
908
909 typedef SingletonModule<PreferenceSystemAPI> PreferenceSystemModule;
910 typedef Static<PreferenceSystemModule> StaticPreferenceSystemModule;
911 StaticRegisterModule staticRegisterPreferenceSystem( StaticPreferenceSystemModule::instance() );
912
913 void Preferences_Load(){
914         g_GamesDialog.LoadPrefs();
915
916         globalOutputStream() << "loading local preferences from " << g_Preferences.m_inipath->str << "\n";
917
918         if ( !Preferences_Load( g_preferences, g_Preferences.m_inipath->str, g_GamesDialog.m_sGameFile.c_str() ) ) {
919                 globalOutputStream() << "failed to load local preferences from " << g_Preferences.m_inipath->str << "\n";
920         }
921 }
922
923 void Preferences_Save(){
924         if ( g_preferences_globals.disable_ini ) {
925                 return;
926         }
927
928         // save global preferences
929         g_GamesDialog.SavePrefs();
930
931         globalOutputStream() << "saving local preferences to " << g_Preferences.m_inipath->str << "\n";
932
933         if ( !Preferences_Save_Safe( g_preferences, g_Preferences.m_inipath->str ) ) {
934                 globalOutputStream() << "failed to save local preferences to " << g_Preferences.m_inipath->str << "\n";
935         }
936 }
937
938 void Preferences_Reset(){
939         file_remove( g_Preferences.m_inipath->str );
940 }
941
942
943 void PrefsDlg::PostModal( EMessageBoxReturn code ){
944         if ( code == eIDOK ) {
945                 Preferences_Save();
946                 UpdateAllWindows();
947         }
948 }
949
950 std::vector<const char*> g_restart_required;
951
952 void PreferencesDialog_restartRequired( const char* staticName ){
953         g_restart_required.push_back( staticName );
954 }
955
956 bool PreferencesDialog_isRestartRequired(){
957         return !g_restart_required.empty();
958 }
959
960 void PreferencesDialog_restartIfRequired(){
961                 if ( !g_restart_required.empty() ) {
962                         StringOutputStream message( 256 );
963                 message << "Preference changes require a restart:\n\n";
964
965                         for ( std::vector<const char*>::iterator i = g_restart_required.begin(); i != g_restart_required.end(); ++i )
966                         {
967                                 message << ( *i ) << '\n';
968                         }
969
970                 message << "\nRestart now?";
971
972                 auto ret = ui::alert( MainFrame_getWindow(), message.c_str(), "Restart " RADIANT_NAME "?", ui::alert_type::YESNO, ui::alert_icon::Question );
973
974                         g_restart_required.clear();
975
976                 if ( ret == ui::alert_response::YES ) {
977                         g_GamesDialog.m_bSkipGamePromptOnce = true;
978                         Radiant_Restart();
979                 }
980         }
981 }
982
983 void PreferencesDialog_showDialog(){
984         if ( ConfirmModified( "Edit Preferences" ) && g_Preferences.DoModal() == eIDOK ) {
985                 PreferencesDialog_restartIfRequired();
986         }
987 }
988
989 struct GameName {
990         static void Export(const Callback<void(const char *)> &returnz) {
991                 returnz(gamename_get());
992         }
993
994         static void Import(const char *value) {
995                 gamename_set(value);
996         }
997 };
998
999 struct GameMode {
1000         static void Export(const Callback<void(const char *)> &returnz) {
1001                 returnz(gamemode_get());
1002         }
1003
1004         static void Import(const char *value) {
1005                 gamemode_set(value);
1006         }
1007 };
1008
1009 void RegisterPreferences( PreferenceSystem& preferences ){
1010         preferences.registerPreference( "CustomShaderEditorCommand", make_property_string( g_TextEditor_editorCommand ) );
1011
1012         preferences.registerPreference( "GameName", make_property<GameName>() );
1013         preferences.registerPreference( "GameMode", make_property<GameMode>() );
1014 }
1015
1016 void Preferences_Init(){
1017         RegisterPreferences( GetPreferenceSystem() );
1018 }