summaryrefslogtreecommitdiffstats
path: root/xbmc/games/dialogs/osd/DialogInGameSaves.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/games/dialogs/osd/DialogInGameSaves.cpp')
-rw-r--r--xbmc/games/dialogs/osd/DialogInGameSaves.cpp426
1 files changed, 426 insertions, 0 deletions
diff --git a/xbmc/games/dialogs/osd/DialogInGameSaves.cpp b/xbmc/games/dialogs/osd/DialogInGameSaves.cpp
new file mode 100644
index 0000000..9f66fd5
--- /dev/null
+++ b/xbmc/games/dialogs/osd/DialogInGameSaves.cpp
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2020-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DialogInGameSaves.h"
+
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "XBDateTime.h"
+#include "cores/RetroPlayer/guibridge/GUIGameRenderManager.h"
+#include "cores/RetroPlayer/guibridge/GUIGameSettingsHandle.h"
+#include "cores/RetroPlayer/guicontrols/GUIGameControl.h"
+#include "cores/RetroPlayer/playback/IPlayback.h"
+#include "cores/RetroPlayer/savestates/ISavestate.h"
+#include "cores/RetroPlayer/savestates/SavestateDatabase.h"
+#include "dialogs/GUIDialogContextMenu.h"
+#include "dialogs/GUIDialogOK.h"
+#include "dialogs/GUIDialogYesNo.h"
+#include "games/dialogs/DialogGameDefines.h"
+#include "guilib/GUIKeyboardFactory.h"
+#include "guilib/GUIMessage.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/WindowIDs.h"
+#include "settings/GameSettings.h"
+#include "settings/MediaSettings.h"
+#include "utils/log.h"
+
+using namespace KODI;
+using namespace GAME;
+using namespace RETRO;
+
+namespace
+{
+CFileItemPtr CreateNewSaveItem()
+{
+ CFileItemPtr item = std::make_shared<CFileItem>(g_localizeStrings.Get(15314)); // "Save"
+
+ // A nonexistent path ensures a gamewindow control won't render any pixels
+ item->SetPath(NO_PIXEL_DATA);
+ item->SetArt("icon", "DefaultAddSource.png");
+ item->SetProperty(SAVESTATE_CAPTION,
+ g_localizeStrings.Get(15315)); // "Save progress to a new save file"
+
+ return item;
+}
+} // namespace
+
+CDialogInGameSaves::CDialogInGameSaves()
+ : CDialogGameVideoSelect(WINDOW_DIALOG_IN_GAME_SAVES), m_newSaveItem(CreateNewSaveItem())
+{
+}
+
+bool CDialogInGameSaves::OnMessage(CGUIMessage& message)
+{
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_REFRESH_THUMBS:
+ {
+ if (message.GetControlId() == GetID())
+ {
+ const std::string& itemPath = message.GetStringParam();
+ CGUIListItemPtr itemInfo = message.GetItem();
+
+ if (!itemPath.empty())
+ {
+ OnItemRefresh(itemPath, std::move(itemInfo));
+ }
+ else
+ {
+ InitSavedGames();
+ RefreshList();
+ }
+
+ return true;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return CDialogGameVideoSelect::OnMessage(message);
+}
+
+std::string CDialogInGameSaves::GetHeading()
+{
+ return g_localizeStrings.Get(35249); // "Save / Load"
+}
+
+void CDialogInGameSaves::PreInit()
+{
+ InitSavedGames();
+}
+
+void CDialogInGameSaves::InitSavedGames()
+{
+ m_savestateItems.Clear();
+
+ auto gameSettings = CServiceBroker::GetGameRenderManager().RegisterGameSettingsDialog();
+
+ CSavestateDatabase db;
+ db.GetSavestatesNav(m_savestateItems, gameSettings->GetPlayingGame(),
+ gameSettings->GameClientID());
+
+ m_savestateItems.Sort(SortByDate, SortOrderDescending);
+}
+
+void CDialogInGameSaves::GetItems(CFileItemList& items)
+{
+ items.Add(m_newSaveItem);
+ std::for_each(m_savestateItems.cbegin(), m_savestateItems.cend(),
+ [&items](const auto& item) { items.Add(item); });
+}
+
+void CDialogInGameSaves::OnItemFocus(unsigned int index)
+{
+ if (static_cast<int>(index) < 1 + m_savestateItems.Size())
+ m_focusedItemIndex = index;
+}
+
+unsigned int CDialogInGameSaves::GetFocusedItem() const
+{
+ return m_focusedControl;
+}
+
+void CDialogInGameSaves::OnItemRefresh(const std::string& itemPath, CGUIListItemPtr itemInfo)
+{
+ // Turn the message params into a savestate item
+ CFileItemPtr item = TranslateMessageItem(itemPath, std::move(itemInfo));
+ if (item)
+ {
+ // Look up existing savestate by path
+ auto it =
+ std::find_if(m_savestateItems.cbegin(), m_savestateItems.cend(),
+ [&itemPath](const CFileItemPtr& item) { return item->GetPath() == itemPath; });
+
+ // Update savestate or add a new one
+ if (it != m_savestateItems.cend())
+ **it = std::move(*item);
+ else
+ m_savestateItems.AddFront(std::move(item), 0);
+
+ RefreshList();
+ }
+}
+
+void CDialogInGameSaves::PostExit()
+{
+ m_savestateItems.Clear();
+}
+
+bool CDialogInGameSaves::OnClickAction()
+{
+ if (static_cast<int>(m_focusedItemIndex) < 1 + m_savestateItems.Size())
+ {
+ if (m_focusedItemIndex <= 0)
+ {
+ OnNewSave();
+ return true;
+ }
+ else
+ {
+ CFileItemPtr focusedItem = m_savestateItems[m_focusedItemIndex - 1];
+ if (focusedItem)
+ {
+ OnLoad(*focusedItem);
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool CDialogInGameSaves::OnMenuAction()
+{
+ // Start at index 1 to account for leading "Save" item
+ if (1 <= m_focusedItemIndex && static_cast<int>(m_focusedItemIndex) < 1 + m_savestateItems.Size())
+ {
+ CFileItemPtr focusedItem = m_savestateItems[m_focusedItemIndex - 1];
+ if (focusedItem)
+ {
+ CContextButtons buttons;
+
+ buttons.Add(0, 13206); // "Overwrite"
+ buttons.Add(1, 118); // "Rename"
+ buttons.Add(2, 117); // "Delete"
+
+ const int index = CGUIDialogContextMenu::Show(buttons);
+
+ if (index == 0)
+ OnOverwrite(*focusedItem);
+ if (index == 1)
+ OnRename(*focusedItem);
+ else if (index == 2)
+ OnDelete(*focusedItem);
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool CDialogInGameSaves::OnOverwriteAction()
+{
+ // Start at index 1 to account for leading "Save" item
+ if (1 <= m_focusedItemIndex && static_cast<int>(m_focusedItemIndex) < 1 + m_savestateItems.Size())
+ {
+ CFileItemPtr focusedItem = m_savestateItems[m_focusedItemIndex - 1];
+ if (focusedItem)
+ {
+ OnOverwrite(*focusedItem);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool CDialogInGameSaves::OnRenameAction()
+{
+ // Start at index 1 to account for leading "Save" item
+ if (1 <= m_focusedItemIndex && static_cast<int>(m_focusedItemIndex) < 1 + m_savestateItems.Size())
+ {
+ CFileItemPtr focusedItem = m_savestateItems[m_focusedItemIndex - 1];
+ if (focusedItem)
+ {
+ OnRename(*focusedItem);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool CDialogInGameSaves::OnDeleteAction()
+{
+ // Start at index 1 to account for leading "Save" item
+ if (1 <= m_focusedItemIndex && static_cast<int>(m_focusedItemIndex) < 1 + m_savestateItems.Size())
+ {
+ CFileItemPtr focusedItem = m_savestateItems[m_focusedItemIndex - 1];
+ if (focusedItem)
+ {
+ OnDelete(*focusedItem);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void CDialogInGameSaves::OnNewSave()
+{
+ auto gameSettings = CServiceBroker::GetGameRenderManager().RegisterGameSettingsDialog();
+
+ const std::string savestatePath = gameSettings->CreateSavestate(false);
+ if (savestatePath.empty())
+ {
+ // "Error"
+ // "An unknown error has occurred."
+ CGUIDialogOK::ShowAndGetInput(257, 24071);
+ return;
+ }
+
+ // Create a simulated savestate to update the GUI faster. We will be notified
+ // of the real savestate info via OnMessage() when the savestate creation
+ // completes.
+ auto savestate = RETRO::CSavestateDatabase::AllocateSavestate();
+
+ savestate->SetType(SAVE_TYPE::MANUAL);
+ savestate->SetCreated(CDateTime::GetUTCDateTime());
+
+ savestate->Finalize();
+
+ CFileItemPtr item = std::make_shared<CFileItem>();
+ CSavestateDatabase::GetSavestateItem(*savestate, savestatePath, *item);
+
+ m_savestateItems.AddFront(std::move(item), 0);
+
+ RefreshList();
+}
+
+void CDialogInGameSaves::OnLoad(CFileItem& focusedItem)
+{
+ auto gameSettings = CServiceBroker::GetGameRenderManager().RegisterGameSettingsDialog();
+
+ // Load savestate
+ if (gameSettings->LoadSavestate(focusedItem.GetPath()))
+ {
+ // Close OSD on successful load
+ gameSettings->CloseOSD();
+ }
+ else
+ {
+ // "Error"
+ // "An unknown error has occurred."
+ CGUIDialogOK::ShowAndGetInput(257, 24071);
+ }
+}
+
+void CDialogInGameSaves::OnOverwrite(CFileItem& focusedItem)
+{
+ std::string savestatePath = focusedItem.GetPath();
+ if (savestatePath.empty())
+ return;
+
+ auto gameSettings = CServiceBroker::GetGameRenderManager().RegisterGameSettingsDialog();
+
+ // Update savestate
+ if (gameSettings->UpdateSavestate(savestatePath))
+ {
+ // Create a simulated savestate to update the GUI faster. We will be
+ // notified of the real savestate info via OnMessage() when the
+ // overwriting completes.
+ auto savestate = RETRO::CSavestateDatabase::AllocateSavestate();
+
+ savestate->SetType(SAVE_TYPE::MANUAL);
+ savestate->SetLabel(focusedItem.GetProperty(SAVESTATE_LABEL).asString());
+ savestate->SetCaption(focusedItem.GetProperty(SAVESTATE_CAPTION).asString());
+ savestate->SetCreated(CDateTime::GetUTCDateTime());
+
+ savestate->Finalize();
+
+ CSavestateDatabase::GetSavestateItem(*savestate, savestatePath, focusedItem);
+
+ RefreshList();
+ }
+ else
+ {
+ // Log an error and notify the user
+ CLog::Log(LOGERROR, "Failed to overwrite savestate at {}", CURL::GetRedacted(savestatePath));
+
+ // "Error"
+ // "An unknown error has occurred."
+ CGUIDialogOK::ShowAndGetInput(257, 24071);
+ }
+}
+
+void CDialogInGameSaves::OnRename(CFileItem& focusedItem)
+{
+ const std::string& savestatePath = focusedItem.GetPath();
+ if (savestatePath.empty())
+ return;
+
+ RETRO::CSavestateDatabase db;
+
+ std::string label;
+
+ std::unique_ptr<RETRO::ISavestate> savestate = RETRO::CSavestateDatabase::AllocateSavestate();
+ if (db.GetSavestate(savestatePath, *savestate))
+ label = savestate->Label();
+
+ // "Enter new filename"
+ if (CGUIKeyboardFactory::ShowAndGetInput(label, CVariant{g_localizeStrings.Get(16013)}, true) &&
+ label != savestate->Label())
+ {
+ std::unique_ptr<RETRO::ISavestate> newSavestate = db.RenameSavestate(savestatePath, label);
+ if (newSavestate)
+ {
+ RETRO::CSavestateDatabase::GetSavestateItem(*newSavestate, savestatePath, focusedItem);
+
+ RefreshList();
+ }
+ else
+ {
+ // "Error"
+ // "An unknown error has occurred."
+ CGUIDialogOK::ShowAndGetInput(257, 24071);
+ }
+ }
+}
+
+void CDialogInGameSaves::OnDelete(CFileItem& focusedItem)
+{
+ // "Confirm delete"
+ // "Would you like to delete the selected file(s)?[CR]Warning - this action can't be undone!"
+ if (CGUIDialogYesNo::ShowAndGetInput(CVariant{122}, CVariant{125}))
+ {
+ RETRO::CSavestateDatabase db;
+ if (db.DeleteSavestate(focusedItem.GetPath()))
+ {
+ m_savestateItems.Remove(&focusedItem);
+
+ RefreshList();
+
+ auto gameSettings = CServiceBroker::GetGameRenderManager().RegisterGameSettingsDialog();
+ gameSettings->FreeSavestateResources(focusedItem.GetPath());
+ }
+ else
+ {
+ // "Error"
+ // "An unknown error has occurred."
+ CGUIDialogOK::ShowAndGetInput(257, 24071);
+ }
+ }
+}
+
+CFileItemPtr CDialogInGameSaves::TranslateMessageItem(const std::string& messagePath,
+ CGUIListItemPtr messageItem)
+{
+ CFileItemPtr item;
+
+ if (messageItem && messageItem->IsFileItem())
+ item = std::static_pointer_cast<CFileItem>(messageItem);
+ else if (messageItem)
+ item = std::make_shared<CFileItem>(*messageItem);
+ else if (!messagePath.empty())
+ {
+ item = std::make_shared<CFileItem>();
+
+ // Load savestate if no item info was given
+ auto savestate = RETRO::CSavestateDatabase::AllocateSavestate();
+ RETRO::CSavestateDatabase db;
+ if (db.GetSavestate(messagePath, *savestate))
+ RETRO::CSavestateDatabase::GetSavestateItem(*savestate, messagePath, *item);
+ else
+ item.reset();
+ }
+
+ return item;
+}