]> de.git.xonotic.org Git - xonotic/netradiant.git/blobdiff - radiant/preferences.cpp
radiant/preferences: fix memory issue when saving pref
[xonotic/netradiant.git] / radiant / preferences.cpp
index 9a3e826c19a46f0d51b982c710e527ac3d59c62b..e57380eb1f3a32e071501d901b365762cb4fb417 100644 (file)
@@ -81,7 +81,7 @@ void Mouse_constructPage( PreferenceGroup& group ){
        Mouse_constructPreferences( page );
 }
 void Mouse_registerPreferencesPage(){
-       PreferencesDialog_addInterfacePage( FreeCaller1<PreferenceGroup&, Mouse_constructPage>() );
+       PreferencesDialog_addInterfacePage( makeCallbackF(Mouse_constructPage) );
 }
 
 
@@ -121,7 +121,7 @@ CGameDescription::CGameDescription( xmlDocPtr pDoc, const CopiedString& gameFile
 
        {
                StringOutputStream path( 256 );
-               path << AppPath_get() << gameFile.c_str() << "/";
+               path << DataPath_get() << "gamepacks/" << gameFile.c_str() << "/";
                mGameToolsPath = path.c_str();
        }
 
@@ -204,30 +204,32 @@ bool Preferences_Save( PreferenceDictionary& preferences, const char* filename )
 }
 
 bool Preferences_Save_Safe( PreferenceDictionary& preferences, const char* filename ){
-       Array<char> tmpName( filename, filename + strlen( filename ) + 1 + 3 );
-       *( tmpName.end() - 4 ) = 'T';
-       *( tmpName.end() - 3 ) = 'M';
-       *( tmpName.end() - 2 ) = 'P';
-       *( tmpName.end() - 1 ) = '\0';
+       std::string tmpName( filename );
+       tmpName += "TMP";
 
-       return Preferences_Save( preferences, tmpName.data() )
+       return Preferences_Save( preferences, tmpName.c_str() )
                   && ( !file_exists( filename ) || file_remove( filename ) )
                   && file_move( tmpName.data(), filename );
 }
 
 
+struct LogConsole {
+       static void Export(const Callback<void(bool)> &returnz) {
+               returnz(g_Console_enableLogging);
+       }
 
-void LogConsole_importString( const char* string ){
-       g_Console_enableLogging = string_equal( string, "true" );
-       Sys_LogFile( g_Console_enableLogging );
-}
-typedef FreeCaller1<const char*, LogConsole_importString> LogConsoleImportStringCaller;
+       static void Import(bool value) {
+               g_Console_enableLogging = value;
+               Sys_LogFile(g_Console_enableLogging);
+       }
+};
 
 
 void RegisterGlobalPreferences( PreferenceSystem& preferences ){
-       preferences.registerPreference( "gamefile", CopiedStringImportStringCaller( g_GamesDialog.m_sGameFile ), CopiedStringExportStringCaller( g_GamesDialog.m_sGameFile ) );
-       preferences.registerPreference( "gamePrompt", BoolImportStringCaller( g_GamesDialog.m_bGamePrompt ), BoolExportStringCaller( g_GamesDialog.m_bGamePrompt ) );
-       preferences.registerPreference( "log console", LogConsoleImportStringCaller(), BoolExportStringCaller( g_Console_enableLogging ) );
+       preferences.registerPreference( "gamefile", make_property_string( g_GamesDialog.m_sGameFile ) );
+       preferences.registerPreference( "gamePrompt", make_property_string( g_GamesDialog.m_bGamePrompt ) );
+       preferences.registerPreference( "skipGamePromptOnce", make_property_string( g_GamesDialog.m_bSkipGamePromptOnce ) );
+       preferences.registerPreference( "log console", make_property_string<LogConsole>() );
 }
 
 
@@ -277,10 +279,21 @@ void CGameDialog::GameFileImport( int value ){
        {
                ++iGame;
        }
-       m_sGameFile = ( *iGame )->mGameFile;
+
+       if ( ( *iGame )->mGameFile != m_sGameFile ) {
+               m_sGameFile = ( *iGame )->mGameFile;
+
+               // do not trigger radiant restart when switching game on startup using Global Preferences dialog
+               if ( !onStartup ) {
+                       PreferencesDialog_restartRequired( "Selected Game" );
+               }
+       }
+
+       // onStartup can only be true once, when Global Preferences are displayed at startup
+       onStartup = false;
 }
 
-void CGameDialog::GameFileExport( const IntImportCallback& importCallback ) const {
+void CGameDialog::GameFileExport( const Callback<void(int)> & importCallback ) const {
        // use m_sGameFile to set value
        std::list<CGameDescription *>::const_iterator iGame;
        int i = 0;
@@ -295,13 +308,15 @@ void CGameDialog::GameFileExport( const IntImportCallback& importCallback ) cons
        importCallback( m_nComboSelect );
 }
 
-void CGameDialog_GameFileImport( CGameDialog& self, int value ){
-       self.GameFileImport( value );
-}
+struct CGameDialog_GameFile {
+       static void Export(const CGameDialog &self, const Callback<void(int)> &returnz) {
+               self.GameFileExport(returnz);
+       }
 
-void CGameDialog_GameFileExport( CGameDialog& self, const IntImportCallback& importCallback ){
-       self.GameFileExport( importCallback );
-}
+       static void Import(CGameDialog &self, int value) {
+               self.GameFileImport(value);
+       }
+};
 
 void CGameDialog::CreateGlobalFrame( PreferencesPage& page ){
        std::vector<const char*> games;
@@ -313,8 +328,7 @@ void CGameDialog::CreateGlobalFrame( PreferencesPage& page ){
        page.appendCombo(
                "Select the game",
                StringArrayRange( &( *games.begin() ), &( *games.end() ) ),
-               ReferenceCaller1<CGameDialog, int, CGameDialog_GameFileImport>( *this ),
-               ReferenceCaller1<CGameDialog, const IntImportCallback&, CGameDialog_GameFileExport>( *this )
+               make_property<CGameDialog_GameFile>(*this)
                );
        page.appendCheckBox( "Startup", "Show Global Preferences", m_bGamePrompt );
 }
@@ -326,7 +340,7 @@ ui::Window CGameDialog::BuildDialog(){
        frame.add(vbox2);
 
        {
-               PreferencesPage preferencesPage( *this, ui::Widget(GTK_WIDGET( vbox2 )) );
+               PreferencesPage preferencesPage( *this, vbox2 );
                Global_constructPreferences( preferencesPage );
                CreateGlobalFrame( preferencesPage );
        }
@@ -334,36 +348,9 @@ ui::Window CGameDialog::BuildDialog(){
        return create_simple_modal_dialog_window( "Global Preferences", m_modal, frame );
 }
 
-class LoadGameFile
-{
-std::list<CGameDescription*>& mGames;
-const char* mPath;
-public:
-LoadGameFile( std::list<CGameDescription*>& games, const char* path ) : mGames( games ), mPath( path ){
-}
-void operator()( const char* name ) const {
-       if ( !extension_equal( path_get_extension( name ), "game" ) ) {
-               return;
-       }
-       StringOutputStream strPath( 256 );
-       strPath << mPath << name;
-       globalOutputStream() << strPath.c_str() << '\n';
-
-       xmlDocPtr pDoc = xmlParseFile( strPath.c_str() );
-       if ( pDoc ) {
-               mGames.push_front( new CGameDescription( pDoc, name ) );
-               xmlFreeDoc( pDoc );
-       }
-       else
-       {
-               globalErrorStream() << "XML parser failed on '" << strPath.c_str() << "'\n";
-       }
-}
-};
-
 void CGameDialog::ScanForGames(){
        StringOutputStream strGamesPath( 256 );
-       strGamesPath << AppPath_get() << "games/";
+       strGamesPath << DataPath_get() << "gamepacks/games/";
        const char *path = strGamesPath.c_str();
 
        globalOutputStream() << "Scanning for game description files: " << path << '\n';
@@ -377,7 +364,22 @@ void CGameDialog::ScanForGames(){
           (if that's really needed)
         */
 
-       Directory_forEach( path, LoadGameFile( mGames, path ) );
+       Directory_forEach(path, [&](const char *name) {
+               if (!extension_equal(path_get_extension(name), "game")) {
+                       return;
+               }
+               StringOutputStream strPath(256);
+               strPath << path << name;
+               globalOutputStream() << strPath.c_str() << '\n';
+
+               xmlDocPtr pDoc = xmlParseFile(strPath.c_str());
+               if (pDoc) {
+                       mGames.push_front(new CGameDescription(pDoc, name));
+                       xmlFreeDoc(pDoc);
+               } else {
+                       globalErrorStream() << "XML parser failed on '" << strPath.c_str() << "'\n";
+               }
+       });
 }
 
 CGameDescription* CGameDialog::GameDescriptionForComboItem(){
@@ -406,9 +408,12 @@ void CGameDialog::Reset(){
 }
 
 void CGameDialog::Init(){
+       bool gamePrompt = false;
+
        InitGlobalPrefPath();
        LoadPrefs();
        ScanForGames();
+
        if ( mGames.empty() ) {
                Error( "Didn't find any valid game file descriptions, aborting\n" );
        }
@@ -429,7 +434,15 @@ void CGameDialog::Init(){
 
        CGameDescription* currentGameDescription = 0;
 
-       if ( !m_bGamePrompt ) {
+       // m_bSkipGamePromptOnce is used to not prompt for game on restart, only on fresh startup
+       if ( m_bGamePrompt && !m_bSkipGamePromptOnce ) {
+               gamePrompt = true;
+       }
+
+       m_bSkipGamePromptOnce = false;
+       g_GamesDialog.SavePrefs();
+
+       if ( !gamePrompt ) {
                // search by .game name
                std::list<CGameDescription *>::iterator iGame;
                for ( iGame = mGames.begin(); iGame != mGames.end(); ++iGame )
@@ -440,13 +453,19 @@ void CGameDialog::Init(){
                        }
                }
        }
-       if ( m_bGamePrompt || !currentGameDescription ) {
+
+       if ( gamePrompt || !currentGameDescription ) {
+               onStartup = true;
                Create();
                DoGameDialog();
                // use m_nComboSelect to identify the game to run as and set the globals
                currentGameDescription = GameDescriptionForComboItem();
                ASSERT_NOTNULL( currentGameDescription );
        }
+       else {
+               onStartup = false;
+       }
+
        g_pGameDescription = currentGameDescription;
 
        g_pGameDescription->Dump();
@@ -482,8 +501,8 @@ CGameDialog g_GamesDialog;
 
 static void OnButtonClean( ui::Widget widget, gpointer data ){
        // make sure this is what the user wants
-       if ( ui::Widget(GTK_WIDGET( g_Preferences.GetWidget() )).alert( "This will close Radiant and clean the corresponding registry entries.\n"
-                                                                                                                                 "Next time you start Radiant it will be good as new. Do you wish to continue?",
+       if ( ui::alert( g_Preferences.GetWidget(), "This will close " RADIANT_NAME " and clean the corresponding registry entries.\n"
+                                                                                                                                 "Next time you start " RADIANT_NAME " it will be good as new. Do you wish to continue?",
                                                 "Reset Registry", ui::alert_type::YESNO, ui::alert_icon::Asterisk ) == ui::alert_response::YES ) {
                PrefsDlg *dlg = (PrefsDlg*)data;
                dlg->EndModal( eIDCANCEL );
@@ -542,7 +561,7 @@ void PrefsDlg::showPrefPage( ui::Widget prefpage ){
        return;
 }
 
-static void treeSelection( GtkTreeSelection* selection, gpointer data ){
+static void treeSelection( ui::TreeSelection selection, gpointer data ){
        PrefsDlg *dlg = (PrefsDlg*)data;
 
        GtkTreeModel* model;
@@ -629,18 +648,18 @@ void Widget_connectToggleDependency( ui::Widget self, ui::Widget toggleButton ){
 }
 
 
-inline ui::Widget getVBox( ui::Widget page ){
-       return ui::Widget(gtk_bin_get_child( GTK_BIN( page ) ));
+inline ui::VBox getVBox( ui::Bin page ){
+       return ui::VBox::from(gtk_bin_get_child(page));
 }
 
-GtkTreeIter PreferenceTree_appendPage( GtkTreeStore* store, GtkTreeIter* parent, const char* name, ui::Widget page ){
+GtkTreeIter PreferenceTree_appendPage( ui::TreeStore store, GtkTreeIter* parent, const char* name, ui::Widget page ){
        GtkTreeIter group;
        gtk_tree_store_append( store, &group, parent );
        gtk_tree_store_set( store, &group, 0, name, 1, page, -1 );
        return group;
 }
 
-ui::Widget PreferencePages_addPage( ui::Widget notebook, const char* name ){
+ui::Bin PreferencePages_addPage( ui::Widget notebook, const char* name ){
        ui::Widget preflabel = ui::Label( name );
        preflabel.show();
 
@@ -663,27 +682,30 @@ class PreferenceTreeGroup : public PreferenceGroup
 {
 Dialog& m_dialog;
 ui::Widget m_notebook;
-GtkTreeStore* m_store;
+ui::TreeStore m_store;
 GtkTreeIter m_group;
 public:
-PreferenceTreeGroup( Dialog& dialog, ui::Widget notebook, GtkTreeStore* store, GtkTreeIter group ) :
+PreferenceTreeGroup( Dialog& dialog, ui::Widget notebook, ui::TreeStore store, GtkTreeIter group ) :
        m_dialog( dialog ),
        m_notebook( notebook ),
        m_store( store ),
        m_group( group ){
 }
 PreferencesPage createPage( const char* treeName, const char* frameName ){
-       ui::Widget page = PreferencePages_addPage( m_notebook, frameName );
+       auto page = PreferencePages_addPage( m_notebook, frameName );
        PreferenceTree_appendPage( m_store, &m_group, treeName, page );
        return PreferencesPage( m_dialog, getVBox( page ) );
 }
 };
 
 ui::Window PrefsDlg::BuildDialog(){
-       PreferencesDialog_addInterfacePreferences( FreeCaller1<PreferencesPage&, Interface_constructPreferences>() );
+       PreferencesDialog_addInterfacePreferences( makeCallbackF(Interface_constructPreferences) );
        Mouse_registerPreferencesPage();
 
-       ui::Window dialog = ui::Window(create_floating_window( "NetRadiant Preferences", m_parent ));
+       ui::Window dialog = ui::Window(create_floating_window( RADIANT_NAME " Preferences", m_parent ));
+
+       gtk_window_set_transient_for( dialog, m_parent );
+       gtk_window_set_position( dialog, GTK_WIN_POS_CENTER_ON_PARENT );
 
        {
                auto mainvbox = ui::VBox( FALSE, 5 );
@@ -723,7 +745,7 @@ ui::Window PrefsDlg::BuildDialog(){
                                gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( sc_win ), GTK_SHADOW_IN );
 
                                // prefs pages notebook
-                               m_notebook = ui::Widget(gtk_notebook_new());
+                               m_notebook = ui::Widget::from(gtk_notebook_new());
                                // hide the notebook tabs since its not supposed to look like a notebook
                                gtk_notebook_set_show_tabs( GTK_NOTEBOOK( m_notebook ), FALSE );
                                hbox.pack_start( m_notebook, TRUE, TRUE, 0 );
@@ -731,19 +753,19 @@ ui::Window PrefsDlg::BuildDialog(){
 
 
                                {
-                                       auto store = gtk_tree_store_new( 2, G_TYPE_STRING, G_TYPE_POINTER );
+                                       auto store = ui::TreeStore::from(gtk_tree_store_new( 2, G_TYPE_STRING, G_TYPE_POINTER ));
 
-                                       ui::Widget view = ui::TreeView(ui::TreeModel( GTK_TREE_MODEL( store ) ));
-                                       gtk_tree_view_set_headers_visible( GTK_TREE_VIEW( view ), FALSE );
+                                       auto view = ui::TreeView(ui::TreeModel::from(store._handle));
+                                       gtk_tree_view_set_headers_visible(view, FALSE );
 
                                        {
                                                auto renderer = ui::CellRendererText(ui::New);
-                                               GtkTreeViewColumn* column = ui::TreeViewColumn( "Preferences", renderer, {{"text", 0}} );
-                                               gtk_tree_view_append_column( GTK_TREE_VIEW( view ), column );
+                        auto column = ui::TreeViewColumn( "Preferences", renderer, {{"text", 0}} );
+                                               gtk_tree_view_append_column(view, column );
                                        }
 
                                        {
-                                               auto selection = ui::TreeSelection(gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) ));
+                                               auto selection = ui::TreeSelection::from(gtk_tree_view_get_selection(view));
                                                selection.connect( "changed", G_CALLBACK( treeSelection ), this );
                                        }
 
@@ -760,14 +782,14 @@ ui::Window PrefsDlg::BuildDialog(){
                                                PreferencePages_addPage( m_notebook, "Front Page" );
 
                                                {
-                                                       ui::Widget global = PreferencePages_addPage( m_notebook, "Global Preferences" );
+                                                       auto global = PreferencePages_addPage( m_notebook, "Global Preferences" );
                                                        {
                                                                PreferencesPage preferencesPage( *this, getVBox( global ) );
                                                                Global_constructPreferences( preferencesPage );
                                                        }
-                                                       GtkTreeIter group = PreferenceTree_appendPage( store, 0, "Global", global );
+                            auto group = PreferenceTree_appendPage( store, 0, "Global", global );
                                                        {
-                                                               ui::Widget game = PreferencePages_addPage( m_notebook, "Game" );
+                                                               auto game = PreferencePages_addPage( m_notebook, "Game" );
                                                                PreferencesPage preferencesPage( *this, getVBox( game ) );
                                                                g_GamesDialog.CreateGlobalFrame( preferencesPage );
 
@@ -776,45 +798,45 @@ ui::Window PrefsDlg::BuildDialog(){
                                                }
 
                                                {
-                                                       ui::Widget interfacePage = PreferencePages_addPage( m_notebook, "Interface Preferences" );
+                                                       auto interfacePage = PreferencePages_addPage( m_notebook, "Interface Preferences" );
                                                        {
                                                                PreferencesPage preferencesPage( *this, getVBox( interfacePage ) );
                                                                PreferencesPageCallbacks_constructPage( g_interfacePreferences, preferencesPage );
                                                        }
 
-                                                       GtkTreeIter group = PreferenceTree_appendPage( store, 0, "Interface", interfacePage );
+                            auto group = PreferenceTree_appendPage( store, 0, "Interface", interfacePage );
                                                        PreferenceTreeGroup preferenceGroup( *this, m_notebook, store, group );
 
                                                        PreferenceGroupCallbacks_constructGroup( g_interfaceCallbacks, preferenceGroup );
                                                }
 
                                                {
-                                                       ui::Widget display = PreferencePages_addPage( m_notebook, "Display Preferences" );
+                                                       auto display = PreferencePages_addPage( m_notebook, "Display Preferences" );
                                                        {
                                                                PreferencesPage preferencesPage( *this, getVBox( display ) );
                                                                PreferencesPageCallbacks_constructPage( g_displayPreferences, preferencesPage );
                                                        }
-                                                       GtkTreeIter group = PreferenceTree_appendPage( store, 0, "Display", display );
+                            auto group = PreferenceTree_appendPage( store, 0, "Display", display );
                                                        PreferenceTreeGroup preferenceGroup( *this, m_notebook, store, group );
 
                                                        PreferenceGroupCallbacks_constructGroup( g_displayCallbacks, preferenceGroup );
                                                }
 
                                                {
-                                                       ui::Widget settings = PreferencePages_addPage( m_notebook, "General Settings" );
+                                                       auto settings = PreferencePages_addPage( m_notebook, "General Settings" );
                                                        {
                                                                PreferencesPage preferencesPage( *this, getVBox( settings ) );
                                                                PreferencesPageCallbacks_constructPage( g_settingsPreferences, preferencesPage );
                                                        }
 
-                                                       GtkTreeIter group = PreferenceTree_appendPage( store, 0, "Settings", settings );
+                            auto group = PreferenceTree_appendPage( store, 0, "Settings", settings );
                                                        PreferenceTreeGroup preferenceGroup( *this, m_notebook, store, group );
 
                                                        PreferenceGroupCallbacks_constructGroup( g_settingsCallbacks, preferenceGroup );
                                                }
                                        }
 
-                                       gtk_tree_view_expand_all( GTK_TREE_VIEW( view ) );
+                                       gtk_tree_view_expand_all(view );
 
                                        g_object_unref( G_OBJECT( store ) );
                                }
@@ -884,6 +906,7 @@ void Preferences_Save(){
                return;
        }
 
+       // save global preferences
        g_GamesDialog.SavePrefs();
 
        globalOutputStream() << "saving local preferences to " << g_Preferences.m_inipath->str << "\n";
@@ -915,50 +938,57 @@ void PreferencesDialog_showDialog(){
        if ( ConfirmModified( "Edit Preferences" ) && g_Preferences.DoModal() == eIDOK ) {
                if ( !g_restart_required.empty() ) {
                        StringOutputStream message( 256 );
-                       message << "Preference changes require a restart:\n";
+                       message << "Preference changes require a restart:\n\n";
+
                        for ( std::vector<const char*>::iterator i = g_restart_required.begin(); i != g_restart_required.end(); ++i )
                        {
                                message << ( *i ) << '\n';
                        }
-                       MainFrame_getWindow().alert( message.c_str() );
+
+                       message << "\nRestart now?";
+
+                       auto ret = ui::alert( MainFrame_getWindow(), message.c_str(), "Restart " RADIANT_NAME "?", ui::alert_type::YESNO, ui::alert_icon::Question );
+
                        g_restart_required.clear();
+
+                       if ( ret == ui::alert_response::YES ) {
+                               g_GamesDialog.m_bSkipGamePromptOnce = true;
+                               Radiant_Restart();
+                       }
                }
        }
 }
 
+struct GameName {
+       static void Export(const Callback<void(const char *)> &returnz) {
+               returnz(gamename_get());
+       }
 
+       static void Import(const char *value) {
+               gamename_set(value);
+       }
+};
 
+struct GameMode {
+       static void Export(const Callback<void(const char *)> &returnz) {
+               returnz(gamemode_get());
+       }
 
-
-void GameName_importString( const char* value ){
-       gamename_set( value );
-}
-typedef FreeCaller1<const char*, GameName_importString> GameNameImportStringCaller;
-void GameName_exportString( const StringImportCallback& importer ){
-       importer( gamename_get() );
-}
-typedef FreeCaller1<const StringImportCallback&, GameName_exportString> GameNameExportStringCaller;
-
-void GameMode_importString( const char* value ){
-       gamemode_set( value );
-}
-typedef FreeCaller1<const char*, GameMode_importString> GameModeImportStringCaller;
-void GameMode_exportString( const StringImportCallback& importer ){
-       importer( gamemode_get() );
-}
-typedef FreeCaller1<const StringImportCallback&, GameMode_exportString> GameModeExportStringCaller;
-
+       static void Import(const char *value) {
+               gamemode_set(value);
+       }
+};
 
 void RegisterPreferences( PreferenceSystem& preferences ){
 #if GDEF_OS_WINDOWS
-       preferences.registerPreference( "UseCustomShaderEditor", BoolImportStringCaller( g_TextEditor_useWin32Editor ), BoolExportStringCaller( g_TextEditor_useWin32Editor ) );
+       preferences.registerPreference( "UseCustomShaderEditor", make_property_string( g_TextEditor_useWin32Editor ) );
 #else
-       preferences.registerPreference( "UseCustomShaderEditor", BoolImportStringCaller( g_TextEditor_useCustomEditor ), BoolExportStringCaller( g_TextEditor_useCustomEditor ) );
-       preferences.registerPreference( "CustomShaderEditorCommand", CopiedStringImportStringCaller( g_TextEditor_editorCommand ), CopiedStringExportStringCaller( g_TextEditor_editorCommand ) );
+       preferences.registerPreference( "UseCustomShaderEditor", make_property_string( g_TextEditor_useCustomEditor ) );
+       preferences.registerPreference( "CustomShaderEditorCommand", make_property_string( g_TextEditor_editorCommand ) );
 #endif
 
-       preferences.registerPreference( "GameName", GameNameImportStringCaller(), GameNameExportStringCaller() );
-       preferences.registerPreference( "GameMode", GameModeImportStringCaller(), GameModeExportStringCaller() );
+       preferences.registerPreference( "GameName", make_property<GameName>() );
+       preferences.registerPreference( "GameMode", make_property<GameMode>() );
 }
 
 void Preferences_Init(){