summaryrefslogtreecommitdiffstats
path: root/xbmc/games/dialogs/osd
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/games/dialogs/osd')
-rw-r--r--xbmc/games/dialogs/osd/CMakeLists.txt25
-rw-r--r--xbmc/games/dialogs/osd/DialogGameAdvancedSettings.cpp54
-rw-r--r--xbmc/games/dialogs/osd/DialogGameAdvancedSettings.h27
-rw-r--r--xbmc/games/dialogs/osd/DialogGameOSD.cpp81
-rw-r--r--xbmc/games/dialogs/osd/DialogGameOSD.h54
-rw-r--r--xbmc/games/dialogs/osd/DialogGameOSDHelp.cpp71
-rw-r--r--xbmc/games/dialogs/osd/DialogGameOSDHelp.h40
-rw-r--r--xbmc/games/dialogs/osd/DialogGameSaves.cpp500
-rw-r--r--xbmc/games/dialogs/osd/DialogGameSaves.h113
-rw-r--r--xbmc/games/dialogs/osd/DialogGameStretchMode.cpp128
-rw-r--r--xbmc/games/dialogs/osd/DialogGameStretchMode.h51
-rw-r--r--xbmc/games/dialogs/osd/DialogGameVideoFilter.cpp157
-rw-r--r--xbmc/games/dialogs/osd/DialogGameVideoFilter.h47
-rw-r--r--xbmc/games/dialogs/osd/DialogGameVideoRotation.cpp109
-rw-r--r--xbmc/games/dialogs/osd/DialogGameVideoRotation.h44
-rw-r--r--xbmc/games/dialogs/osd/DialogGameVideoSelect.cpp254
-rw-r--r--xbmc/games/dialogs/osd/DialogGameVideoSelect.h84
-rw-r--r--xbmc/games/dialogs/osd/DialogGameVolume.cpp149
-rw-r--r--xbmc/games/dialogs/osd/DialogGameVolume.h80
-rw-r--r--xbmc/games/dialogs/osd/DialogInGameSaves.cpp426
-rw-r--r--xbmc/games/dialogs/osd/DialogInGameSaves.h79
21 files changed, 2573 insertions, 0 deletions
diff --git a/xbmc/games/dialogs/osd/CMakeLists.txt b/xbmc/games/dialogs/osd/CMakeLists.txt
new file mode 100644
index 0000000..3f7f5bd
--- /dev/null
+++ b/xbmc/games/dialogs/osd/CMakeLists.txt
@@ -0,0 +1,25 @@
+set(SOURCES DialogGameAdvancedSettings.cpp
+ DialogGameOSD.cpp
+ DialogGameOSDHelp.cpp
+ DialogGameSaves.cpp
+ DialogGameStretchMode.cpp
+ DialogGameVideoFilter.cpp
+ DialogGameVideoRotation.cpp
+ DialogGameVideoSelect.cpp
+ DialogGameVolume.cpp
+ DialogInGameSaves.cpp
+)
+
+set(HEADERS DialogGameAdvancedSettings.h
+ DialogGameOSD.h
+ DialogGameOSDHelp.h
+ DialogGameSaves.h
+ DialogGameStretchMode.h
+ DialogGameVideoFilter.h
+ DialogGameVideoRotation.h
+ DialogGameVideoSelect.h
+ DialogGameVolume.h
+ DialogInGameSaves.h
+)
+
+core_add_library(gameosddialogs)
diff --git a/xbmc/games/dialogs/osd/DialogGameAdvancedSettings.cpp b/xbmc/games/dialogs/osd/DialogGameAdvancedSettings.cpp
new file mode 100644
index 0000000..83b7a3f
--- /dev/null
+++ b/xbmc/games/dialogs/osd/DialogGameAdvancedSettings.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2018 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 "DialogGameAdvancedSettings.h"
+
+#include "ServiceBroker.h"
+#include "addons/AddonManager.h"
+#include "addons/addoninfo/AddonType.h"
+#include "addons/gui/GUIDialogAddonSettings.h"
+#include "cores/RetroPlayer/guibridge/GUIGameRenderManager.h"
+#include "cores/RetroPlayer/guibridge/GUIGameSettingsHandle.h"
+#include "guilib/GUIMessage.h"
+#include "guilib/WindowIDs.h"
+
+using namespace KODI;
+using namespace GAME;
+
+CDialogGameAdvancedSettings::CDialogGameAdvancedSettings()
+ : CGUIDialog(WINDOW_DIALOG_GAME_ADVANCED_SETTINGS, "")
+{
+}
+
+bool CDialogGameAdvancedSettings::OnMessage(CGUIMessage& message)
+{
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_WINDOW_INIT:
+ {
+ auto gameSettingsHandle = CServiceBroker::GetGameRenderManager().RegisterGameSettingsDialog();
+ if (gameSettingsHandle)
+ {
+ ADDON::AddonPtr addon;
+ if (CServiceBroker::GetAddonMgr().GetAddon(gameSettingsHandle->GameClientID(), addon,
+ ADDON::AddonType::GAMEDLL,
+ ADDON::OnlyEnabled::CHOICE_YES))
+ {
+ gameSettingsHandle.reset();
+ CGUIDialogAddonSettings::ShowForAddon(addon);
+ }
+ }
+
+ return false;
+ }
+ default:
+ break;
+ }
+
+ return CGUIDialog::OnMessage(message);
+}
diff --git a/xbmc/games/dialogs/osd/DialogGameAdvancedSettings.h b/xbmc/games/dialogs/osd/DialogGameAdvancedSettings.h
new file mode 100644
index 0000000..00b669b
--- /dev/null
+++ b/xbmc/games/dialogs/osd/DialogGameAdvancedSettings.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#pragma once
+
+#include "guilib/GUIDialog.h"
+
+namespace KODI
+{
+namespace GAME
+{
+class CDialogGameAdvancedSettings : public CGUIDialog
+{
+public:
+ CDialogGameAdvancedSettings();
+ ~CDialogGameAdvancedSettings() override = default;
+
+ // implementation of CGUIControl via CGUIDialog
+ bool OnMessage(CGUIMessage& message) override;
+};
+} // namespace GAME
+} // namespace KODI
diff --git a/xbmc/games/dialogs/osd/DialogGameOSD.cpp b/xbmc/games/dialogs/osd/DialogGameOSD.cpp
new file mode 100644
index 0000000..3a0eb52
--- /dev/null
+++ b/xbmc/games/dialogs/osd/DialogGameOSD.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017-2018 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 "DialogGameOSD.h"
+
+#include "DialogGameOSDHelp.h"
+#include "ServiceBroker.h"
+#include "games/GameServices.h"
+#include "games/GameSettings.h"
+#include "guilib/WindowIDs.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+
+using namespace KODI;
+using namespace GAME;
+
+CDialogGameOSD::CDialogGameOSD()
+ : CGUIDialog(WINDOW_DIALOG_GAME_OSD, "GameOSD.xml"), m_helpDialog(new CDialogGameOSDHelp(*this))
+{
+ // Initialize CGUIWindow
+ m_loadType = KEEP_IN_MEMORY;
+}
+
+bool CDialogGameOSD::OnAction(const CAction& action)
+{
+ switch (action.GetID())
+ {
+ case ACTION_PARENT_DIR:
+ case ACTION_PREVIOUS_MENU:
+ case ACTION_NAV_BACK:
+ case ACTION_SHOW_OSD:
+ case ACTION_PLAYER_PLAY:
+ {
+ // Disable OSD help if visible
+ if (m_helpDialog->IsVisible() && CServiceBroker::IsServiceManagerUp())
+ {
+ GAME::CGameSettings& gameSettings = CServiceBroker::GetGameServices().GameSettings();
+ if (gameSettings.ShowOSDHelp())
+ {
+ gameSettings.SetShowOSDHelp(false);
+ return true;
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return CGUIDialog::OnAction(action);
+}
+
+void CDialogGameOSD::OnInitWindow()
+{
+ // Init parent class
+ CGUIDialog::OnInitWindow();
+
+ // Init help dialog
+ m_helpDialog->OnInitWindow();
+}
+
+void CDialogGameOSD::OnDeinitWindow(int nextWindowID)
+{
+ CGUIDialog::OnDeinitWindow(nextWindowID);
+
+ if (CServiceBroker::IsServiceManagerUp())
+ {
+ GAME::CGameSettings& gameSettings = CServiceBroker::GetGameServices().GameSettings();
+ gameSettings.SetShowOSDHelp(false);
+ }
+}
+
+bool CDialogGameOSD::PlayInBackground(int dialogId)
+{
+ return dialogId == WINDOW_DIALOG_GAME_VOLUME;
+}
diff --git a/xbmc/games/dialogs/osd/DialogGameOSD.h b/xbmc/games/dialogs/osd/DialogGameOSD.h
new file mode 100644
index 0000000..3a8127b
--- /dev/null
+++ b/xbmc/games/dialogs/osd/DialogGameOSD.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017-2018 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.
+ */
+
+#pragma once
+
+#include "guilib/GUIDialog.h"
+
+#include <memory>
+
+namespace KODI
+{
+namespace GAME
+{
+class CDialogGameOSDHelp;
+
+class CDialogGameOSD : public CGUIDialog
+{
+public:
+ CDialogGameOSD();
+
+ ~CDialogGameOSD() override = default;
+
+ // Implementation of CGUIControl via CGUIDialog
+ bool OnAction(const CAction& action) override;
+
+ // Implementation of CGUIWindow via CGUIDialog
+ void OnDeinitWindow(int nextWindowID) override;
+
+ /*!
+ * \brief Decide if the game should play behind the given dialog
+ *
+ * If true, the game should be played at regular speed.
+ *
+ * \param dialog The current dialog
+ *
+ * \return True if the game should be played at regular speed behind the
+ * dialog, false otherwise
+ */
+ static bool PlayInBackground(int dialogId);
+
+protected:
+ // Implementation of CGUIWindow via CGUIDialog
+ void OnInitWindow() override;
+
+private:
+ std::unique_ptr<CDialogGameOSDHelp> m_helpDialog;
+};
+} // namespace GAME
+} // namespace KODI
diff --git a/xbmc/games/dialogs/osd/DialogGameOSDHelp.cpp b/xbmc/games/dialogs/osd/DialogGameOSDHelp.cpp
new file mode 100644
index 0000000..3152172
--- /dev/null
+++ b/xbmc/games/dialogs/osd/DialogGameOSDHelp.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2018 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 "DialogGameOSDHelp.h"
+
+#include "DialogGameOSD.h"
+#include "ServiceBroker.h"
+#include "games/GameServices.h"
+#include "games/controllers/guicontrols/GUIGameController.h"
+#include "guilib/GUIMessage.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/WindowIDs.h"
+#include "utils/StringUtils.h"
+
+using namespace KODI;
+using namespace GAME;
+
+const int CDialogGameOSDHelp::CONTROL_ID_HELP_TEXT = 1101;
+const int CDialogGameOSDHelp::CONTROL_ID_GAME_CONTROLLER = 1102;
+
+CDialogGameOSDHelp::CDialogGameOSDHelp(CDialogGameOSD& dialog) : m_dialog(dialog)
+{
+}
+
+void CDialogGameOSDHelp::OnInitWindow()
+{
+ // Set help text
+ //! @todo Define Select + X combo elsewhere
+ // "Press {0:s} to open the menu."
+ std::string helpText = StringUtils::Format(g_localizeStrings.Get(35235), "Select + X");
+
+ CGUIMessage msg(GUI_MSG_LABEL_SET, WINDOW_DIALOG_GAME_OSD, CONTROL_ID_HELP_TEXT);
+ msg.SetLabel(helpText);
+ m_dialog.OnMessage(msg);
+
+ // Set controller
+ if (CServiceBroker::IsServiceManagerUp())
+ {
+ CGameServices& gameServices = CServiceBroker::GetGameServices();
+
+ //! @todo Define SNES controller elsewhere
+ ControllerPtr controller = gameServices.GetController("game.controller.snes");
+ if (controller)
+ {
+ //! @todo Activate controller for all game controller controls
+ CGUIGameController* guiController =
+ dynamic_cast<CGUIGameController*>(m_dialog.GetControl(CONTROL_ID_GAME_CONTROLLER));
+ if (guiController != nullptr)
+ guiController->ActivateController(controller);
+ }
+ }
+}
+
+bool CDialogGameOSDHelp::IsVisible()
+{
+ return IsVisible(CONTROL_ID_HELP_TEXT) || IsVisible(CONTROL_ID_GAME_CONTROLLER);
+}
+
+bool CDialogGameOSDHelp::IsVisible(int windowId)
+{
+ CGUIControl* control = m_dialog.GetControl(windowId);
+ if (control != nullptr)
+ return control->IsVisible();
+
+ return false;
+}
diff --git a/xbmc/games/dialogs/osd/DialogGameOSDHelp.h b/xbmc/games/dialogs/osd/DialogGameOSDHelp.h
new file mode 100644
index 0000000..6153075
--- /dev/null
+++ b/xbmc/games/dialogs/osd/DialogGameOSDHelp.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#pragma once
+
+namespace KODI
+{
+namespace GAME
+{
+class CDialogGameOSD;
+
+class CDialogGameOSDHelp
+{
+public:
+ CDialogGameOSDHelp(CDialogGameOSD& dialog);
+
+ // Initialize help controls
+ void OnInitWindow();
+
+ // Check if any help controls are visible
+ bool IsVisible();
+
+private:
+ // Utility functions
+ bool IsVisible(int windowId);
+
+ // Construction parameters
+ CDialogGameOSD& m_dialog;
+
+ // Help control IDs
+ static const int CONTROL_ID_HELP_TEXT;
+ static const int CONTROL_ID_GAME_CONTROLLER;
+};
+} // namespace GAME
+} // namespace KODI
diff --git a/xbmc/games/dialogs/osd/DialogGameSaves.cpp b/xbmc/games/dialogs/osd/DialogGameSaves.cpp
new file mode 100644
index 0000000..90e7785
--- /dev/null
+++ b/xbmc/games/dialogs/osd/DialogGameSaves.cpp
@@ -0,0 +1,500 @@
+/*
+ * 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 "DialogGameSaves.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "addons/Addon.h"
+#include "addons/AddonManager.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/addons/GameClient.h"
+#include "games/dialogs/DialogGameDefines.h"
+#include "guilib/GUIBaseContainer.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIKeyboardFactory.h"
+#include "guilib/GUIMessage.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/WindowIDs.h"
+#include "input/Key.h"
+#include "utils/FileUtils.h"
+#include "utils/Variant.h"
+#include "view/GUIViewControl.h"
+#include "view/ViewState.h"
+
+using namespace KODI;
+using namespace GAME;
+
+CDialogGameSaves::CDialogGameSaves()
+ : CGUIDialog(WINDOW_DIALOG_GAME_SAVES, "DialogSelect.xml"),
+ m_viewControl(std::make_unique<CGUIViewControl>()),
+ m_vecList(std::make_unique<CFileItemList>())
+{
+}
+
+bool CDialogGameSaves::OnMessage(CGUIMessage& message)
+{
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_CLICKED:
+ {
+ const int actionId = message.GetParam1();
+
+ switch (actionId)
+ {
+ case ACTION_SELECT_ITEM:
+ case ACTION_MOUSE_LEFT_CLICK:
+ {
+ int selectedId = m_viewControl->GetSelectedItem();
+ if (0 <= selectedId && selectedId < m_vecList->Size())
+ {
+ CFileItemPtr item = m_vecList->Get(selectedId);
+ if (item)
+ {
+ for (int i = 0; i < m_vecList->Size(); i++)
+ m_vecList->Get(i)->Select(false);
+
+ item->Select(true);
+
+ OnSelect(*item);
+
+ return true;
+ }
+ }
+ break;
+ }
+ case ACTION_CONTEXT_MENU:
+ case ACTION_MOUSE_RIGHT_CLICK:
+ {
+ int selectedItem = m_viewControl->GetSelectedItem();
+ if (selectedItem >= 0 && selectedItem < m_vecList->Size())
+ {
+ CFileItemPtr item = m_vecList->Get(selectedItem);
+ if (item)
+ {
+ OnContextMenu(*item);
+ return true;
+ }
+ }
+ break;
+ }
+ case ACTION_RENAME_ITEM:
+ {
+ const int controlId = message.GetSenderId();
+ if (m_viewControl->HasControl(controlId))
+ {
+ int selectedItem = m_viewControl->GetSelectedItem();
+ if (selectedItem >= 0 && selectedItem < m_vecList->Size())
+ {
+ CFileItemPtr item = m_vecList->Get(selectedItem);
+ if (item)
+ {
+ OnRename(*item);
+ return true;
+ }
+ }
+ }
+ break;
+ }
+ case ACTION_DELETE_ITEM:
+ {
+ const int controlId = message.GetSenderId();
+ if (m_viewControl->HasControl(controlId))
+ {
+ int selectedItem = m_viewControl->GetSelectedItem();
+ if (selectedItem >= 0 && selectedItem < m_vecList->Size())
+ {
+ CFileItemPtr item = m_vecList->Get(selectedItem);
+ if (item)
+ {
+ OnDelete(*item);
+ return true;
+ }
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ const int controlId = message.GetSenderId();
+ switch (controlId)
+ {
+ case CONTROL_SAVES_NEW_BUTTON:
+ {
+ m_bNewPressed = true;
+ Close();
+ break;
+ }
+ case CONTROL_SAVES_CANCEL_BUTTON:
+ {
+ m_selectedItem.reset();
+ m_vecList->Clear();
+ m_bConfirmed = false;
+ Close();
+ break;
+ }
+ default:
+ break;
+ }
+
+ break;
+ }
+
+ case GUI_MSG_SETFOCUS:
+ {
+ const int controlId = message.GetControlId();
+ if (m_viewControl->HasControl(controlId))
+ {
+ if (m_vecList->IsEmpty())
+ {
+ SET_CONTROL_FOCUS(CONTROL_SAVES_NEW_BUTTON, 0);
+ return true;
+ }
+
+ if (m_viewControl->GetCurrentControl() != controlId)
+ {
+ m_viewControl->SetFocused();
+ return true;
+ }
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return CGUIDialog::OnMessage(message);
+}
+
+void CDialogGameSaves::FrameMove()
+{
+ CGUIControl* itemContainer = GetControl(CONTROL_SAVES_DETAILED_LIST);
+ if (itemContainer != nullptr)
+ {
+ if (itemContainer->HasFocus())
+ {
+ int selectedItem = m_viewControl->GetSelectedItem();
+ if (selectedItem >= 0 && selectedItem < m_vecList->Size())
+ {
+ CFileItemPtr item = m_vecList->Get(selectedItem);
+ if (item)
+ OnFocus(*item);
+ }
+ }
+ else
+ {
+ OnFocusLost();
+ }
+ }
+
+ CGUIDialog::FrameMove();
+}
+
+void CDialogGameSaves::OnInitWindow()
+{
+ m_viewControl->SetItems(*m_vecList);
+ m_viewControl->SetCurrentView(CONTROL_SAVES_DETAILED_LIST);
+
+ CGUIDialog::OnInitWindow();
+
+ // Select the first item
+ m_viewControl->SetSelectedItem(0);
+
+ // There's a race condition where the item's focus sends the update message
+ // before the window is fully initialized, so explicitly set the info now.
+ if (!m_vecList->IsEmpty())
+ {
+ CFileItemPtr item = m_vecList->Get(0);
+ if (item)
+ {
+ const std::string gameClientId = item->GetProperty(SAVESTATE_GAME_CLIENT).asString();
+ if (!gameClientId.empty())
+ {
+ std::string emulatorName;
+ std::string emulatorIcon;
+
+ using namespace ADDON;
+
+ AddonPtr addon;
+ CAddonMgr& addonManager = CServiceBroker::GetAddonMgr();
+ if (addonManager.GetAddon(m_currentGameClient, addon, OnlyEnabled::CHOICE_NO))
+ {
+ std::shared_ptr<CGameClient> gameClient = std::dynamic_pointer_cast<CGameClient>(addon);
+ if (gameClient)
+ {
+ m_currentGameClient = gameClient->ID();
+
+ emulatorName = gameClient->GetEmulatorName();
+ emulatorIcon = gameClient->Icon();
+ }
+ }
+
+ if (!emulatorName.empty())
+ {
+ CGUIMessage message(GUI_MSG_LABEL_SET, GetID(), CONTROL_SAVES_EMULATOR_NAME);
+ message.SetLabel(emulatorName);
+ OnMessage(message);
+ }
+ if (!emulatorIcon.empty())
+ {
+ CGUIMessage message(GUI_MSG_SET_FILENAME, GetID(), CONTROL_SAVES_EMULATOR_ICON);
+ message.SetLabel(emulatorIcon);
+ OnMessage(message);
+ }
+ }
+
+ const std::string caption = item->GetProperty(SAVESTATE_CAPTION).asString();
+ if (!caption.empty())
+ {
+ m_currentCaption = caption;
+
+ CGUIMessage message(GUI_MSG_LABEL_SET, GetID(), CONTROL_SAVES_DESCRIPTION);
+ message.SetLabel(m_currentCaption);
+ OnMessage(message);
+ }
+ }
+ }
+}
+
+void CDialogGameSaves::OnDeinitWindow(int nextWindowID)
+{
+ m_viewControl->Clear();
+
+ CGUIDialog::OnDeinitWindow(nextWindowID);
+
+ // Get selected item
+ for (int i = 0; i < m_vecList->Size(); ++i)
+ {
+ CFileItemPtr item = m_vecList->Get(i);
+ if (item->IsSelected())
+ {
+ m_selectedItem = item;
+ break;
+ }
+ }
+
+ m_vecList->Clear();
+}
+
+void CDialogGameSaves::OnWindowLoaded()
+{
+ CGUIDialog::OnWindowLoaded();
+
+ m_viewControl->Reset();
+ m_viewControl->SetParentWindow(GetID());
+ m_viewControl->AddView(GetControl(CONTROL_SAVES_DETAILED_LIST));
+}
+
+void CDialogGameSaves::OnWindowUnload()
+{
+ CGUIDialog::OnWindowUnload();
+ m_viewControl->Reset();
+}
+
+void CDialogGameSaves::Reset()
+{
+ m_bConfirmed = false;
+ m_bNewPressed = false;
+
+ m_vecList->Clear();
+ m_selectedItem.reset();
+}
+
+bool CDialogGameSaves::Open(const std::string& gamePath)
+{
+ CFileItemList items;
+
+ RETRO::CSavestateDatabase db;
+ if (!db.GetSavestatesNav(items, gamePath))
+ return false;
+
+ if (items.IsEmpty())
+ return false;
+
+ items.Sort(SortByDate, SortOrderDescending);
+
+ SetItems(items);
+
+ CGUIDialog::Open();
+
+ return true;
+}
+
+std::string CDialogGameSaves::GetSelectedItemPath()
+{
+ if (m_selectedItem)
+ return m_selectedItem->GetPath();
+
+ return "";
+}
+
+void CDialogGameSaves::SetItems(const CFileItemList& itemList)
+{
+ m_vecList->Clear();
+
+ // Need to make internal copy of list to be sure dialog is owner of it
+ m_vecList->Copy(itemList);
+
+ m_viewControl->SetItems(*m_vecList);
+}
+
+void CDialogGameSaves::OnSelect(const CFileItem& item)
+{
+ m_bConfirmed = true;
+ Close();
+}
+
+void CDialogGameSaves::OnFocus(const CFileItem& item)
+{
+ const std::string caption = item.GetProperty(SAVESTATE_CAPTION).asString();
+ const std::string gameClientId = item.GetProperty(SAVESTATE_GAME_CLIENT).asString();
+
+ HandleCaption(caption);
+ HandleGameClient(gameClientId);
+}
+
+void CDialogGameSaves::OnFocusLost()
+{
+ HandleCaption("");
+ HandleGameClient("");
+}
+
+void CDialogGameSaves::OnContextMenu(CFileItem& item)
+{
+ CContextButtons buttons;
+
+ buttons.Add(0, 118); // "Rename"
+ buttons.Add(1, 117); // "Delete"
+
+ const int index = CGUIDialogContextMenu::Show(buttons);
+
+ if (index == 0)
+ OnRename(item);
+ else if (index == 1)
+ OnDelete(item);
+}
+
+void CDialogGameSaves::OnRename(CFileItem& item)
+{
+ const std::string& savestatePath = item.GetPath();
+
+ // Get savestate properties
+ RETRO::CSavestateDatabase db;
+ std::unique_ptr<RETRO::ISavestate> savestate = RETRO::CSavestateDatabase::AllocateSavestate();
+ db.GetSavestate(savestatePath, *savestate);
+
+ std::string 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, item);
+
+ // Refresh thumbnails
+ m_viewControl->SetItems(*m_vecList);
+ }
+ else
+ {
+ // "Error"
+ // "An unknown error has occurred."
+ CGUIDialogOK::ShowAndGetInput(257, 24071);
+ }
+ }
+}
+
+void CDialogGameSaves::OnDelete(CFileItem& item)
+{
+ // "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}))
+ {
+ const std::string& savestatePath = item.GetPath();
+
+ RETRO::CSavestateDatabase db;
+ if (db.DeleteSavestate(savestatePath))
+ {
+ m_vecList->Remove(&item);
+
+ // Refresh thumbnails
+ m_viewControl->SetItems(*m_vecList);
+ }
+ else
+ {
+ // "Error"
+ // "An unknown error has occurred."
+ CGUIDialogOK::ShowAndGetInput(257, 24071);
+ }
+ }
+}
+
+void CDialogGameSaves::HandleCaption(const std::string& caption)
+{
+ if (caption != m_currentCaption)
+ {
+ m_currentCaption = caption;
+
+ // Update the GUI label
+ CGUIMessage msg(GUI_MSG_LABEL_SET, GetID(), CONTROL_SAVES_DESCRIPTION);
+ msg.SetLabel(m_currentCaption);
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, GetID());
+ }
+}
+
+void CDialogGameSaves::HandleGameClient(const std::string& gameClientId)
+{
+ if (gameClientId == m_currentGameClient)
+ return;
+
+ m_currentGameClient = gameClientId;
+ if (m_currentGameClient.empty())
+ return;
+
+ // Get game client properties
+ std::shared_ptr<CGameClient> gameClient;
+ std::string emulatorName;
+ std::string iconPath;
+
+ using namespace ADDON;
+
+ AddonPtr addon;
+ CAddonMgr& addonManager = CServiceBroker::GetAddonMgr();
+ if (addonManager.GetAddon(m_currentGameClient, addon, OnlyEnabled::CHOICE_NO))
+ gameClient = std::dynamic_pointer_cast<CGameClient>(addon);
+
+ if (gameClient)
+ {
+ emulatorName = gameClient->GetEmulatorName();
+ iconPath = gameClient->Icon();
+ }
+
+ // Update the GUI elements
+ if (!emulatorName.empty())
+ {
+ CGUIMessage message(GUI_MSG_LABEL_SET, GetID(), CONTROL_SAVES_EMULATOR_NAME);
+ message.SetLabel(emulatorName);
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message, GetID());
+ }
+ if (!iconPath.empty())
+ {
+ CGUIMessage message(GUI_MSG_SET_FILENAME, GetID(), CONTROL_SAVES_EMULATOR_ICON);
+ message.SetLabel(iconPath);
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message, GetID());
+ }
+}
diff --git a/xbmc/games/dialogs/osd/DialogGameSaves.h b/xbmc/games/dialogs/osd/DialogGameSaves.h
new file mode 100644
index 0000000..3605ee6
--- /dev/null
+++ b/xbmc/games/dialogs/osd/DialogGameSaves.h
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "guilib/GUIDialog.h"
+
+#include <memory>
+#include <string>
+
+class CFileItem;
+class CFileItemList;
+class CGUIMessage;
+class CGUIViewControl;
+
+namespace KODI
+{
+namespace GAME
+{
+class CDialogGameSaves : public CGUIDialog
+{
+public:
+ CDialogGameSaves();
+ ~CDialogGameSaves() override = default;
+
+ // implementation of CGUIControl via CGUIDialog
+ bool OnMessage(CGUIMessage& message) override;
+
+ // implementation of CGUIWindow via CGUIDialog
+ void FrameMove() override;
+
+ // Player interface
+ void Reset();
+ bool Open(const std::string& gamePath);
+ bool IsConfirmed() const { return m_bConfirmed; }
+ bool IsNewPressed() const { return m_bNewPressed; }
+ std::string GetSelectedItemPath();
+
+protected:
+ // implementation of CGUIWIndow via CGUIDialog
+ void OnInitWindow() override;
+ void OnDeinitWindow(int nextWindowID) override;
+ void OnWindowLoaded() override;
+ void OnWindowUnload() override;
+
+private:
+ using CGUIControl::OnFocus;
+
+ /*!
+ * \breif Called when opening to set the item list
+ */
+ void SetItems(const CFileItemList& itemList);
+
+ /*!
+ * \brief Called when an item has been selected
+ */
+ void OnSelect(const CFileItem& item);
+
+ /*!
+ * \brief Called every frame with the item being focused
+ */
+ void OnFocus(const CFileItem& item);
+
+ /*!
+ * \brief Called every frame if no item is focused
+ */
+ void OnFocusLost();
+
+ /*!
+ * \brief Called when a context menu is opened for an item
+ */
+ void OnContextMenu(CFileItem& item);
+
+ /*!
+ * \brief Called when "Rename" is selected from the context menu
+ */
+ void OnRename(CFileItem& item);
+
+ /*!
+ * \brief Called when "Delete" is selected from the context menu
+ */
+ void OnDelete(CFileItem& item);
+
+ /*!
+ * \brief Called every frame with the caption to set
+ */
+ void HandleCaption(const std::string& caption);
+
+ /*!
+ * \brief Called every frame with the game client to set
+ */
+ void HandleGameClient(const std::string& gameClientId);
+
+ // Dialog parameters
+ std::unique_ptr<CGUIViewControl> m_viewControl;
+ std::unique_ptr<CFileItemList> m_vecList;
+ std::shared_ptr<CFileItem> m_selectedItem;
+
+ // Player parameters
+ bool m_bConfirmed{false};
+ bool m_bNewPressed{false};
+
+ // State parameters
+ std::string m_currentCaption;
+ std::string m_currentGameClient;
+};
+} // namespace GAME
+} // namespace KODI
diff --git a/xbmc/games/dialogs/osd/DialogGameStretchMode.cpp b/xbmc/games/dialogs/osd/DialogGameStretchMode.cpp
new file mode 100644
index 0000000..98c1e07
--- /dev/null
+++ b/xbmc/games/dialogs/osd/DialogGameStretchMode.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2017-2018 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 "DialogGameStretchMode.h"
+
+#include "FileItem.h"
+#include "cores/RetroPlayer/RetroPlayerUtils.h"
+#include "cores/RetroPlayer/guibridge/GUIGameVideoHandle.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/WindowIDs.h"
+#include "settings/GameSettings.h"
+#include "settings/MediaSettings.h"
+#include "utils/Variant.h"
+
+using namespace KODI;
+using namespace GAME;
+
+const std::vector<CDialogGameStretchMode::StretchModeProperties>
+ CDialogGameStretchMode::m_allStretchModes = {
+ {630, RETRO::STRETCHMODE::Normal},
+ // { 631, RETRO::STRETCHMODE::Zoom }, //! @todo RetroArch allows trimming some outer
+ // pixels
+ {632, RETRO::STRETCHMODE::Stretch4x3},
+ {35232, RETRO::STRETCHMODE::Fullscreen},
+ {635, RETRO::STRETCHMODE::Original},
+};
+
+CDialogGameStretchMode::CDialogGameStretchMode()
+ : CDialogGameVideoSelect(WINDOW_DIALOG_GAME_STRETCH_MODE)
+{
+}
+
+std::string CDialogGameStretchMode::GetHeading()
+{
+ return g_localizeStrings.Get(35233); // "Stretch mode"
+}
+
+void CDialogGameStretchMode::PreInit()
+{
+ m_stretchModes.clear();
+
+ for (const auto& stretchMode : m_allStretchModes)
+ {
+ bool bSupported = false;
+
+ switch (stretchMode.stretchMode)
+ {
+ case RETRO::STRETCHMODE::Normal:
+ case RETRO::STRETCHMODE::Original:
+ bSupported = true;
+ break;
+
+ case RETRO::STRETCHMODE::Stretch4x3:
+ case RETRO::STRETCHMODE::Fullscreen:
+ if (m_gameVideoHandle)
+ {
+ bSupported = m_gameVideoHandle->SupportsRenderFeature(RETRO::RENDERFEATURE::STRETCH) ||
+ m_gameVideoHandle->SupportsRenderFeature(RETRO::RENDERFEATURE::PIXEL_RATIO);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (bSupported)
+ m_stretchModes.emplace_back(stretchMode);
+ }
+}
+
+void CDialogGameStretchMode::GetItems(CFileItemList& items)
+{
+ for (const auto& stretchMode : m_stretchModes)
+ {
+ CFileItemPtr item = std::make_shared<CFileItem>(g_localizeStrings.Get(stretchMode.stringIndex));
+
+ const std::string stretchModeId =
+ RETRO::CRetroPlayerUtils::StretchModeToIdentifier(stretchMode.stretchMode);
+ if (!stretchModeId.empty())
+ item->SetProperty("game.stretchmode", CVariant{stretchModeId});
+ items.Add(std::move(item));
+ }
+}
+
+void CDialogGameStretchMode::OnItemFocus(unsigned int index)
+{
+ if (index < m_stretchModes.size())
+ {
+ const RETRO::STRETCHMODE stretchMode = m_stretchModes[index].stretchMode;
+
+ CGameSettings& gameSettings = CMediaSettings::GetInstance().GetCurrentGameSettings();
+ if (gameSettings.StretchMode() != stretchMode)
+ {
+ gameSettings.SetStretchMode(stretchMode);
+ gameSettings.NotifyObservers(ObservableMessageSettingsChanged);
+ }
+ }
+}
+
+unsigned int CDialogGameStretchMode::GetFocusedItem() const
+{
+ CGameSettings& gameSettings = CMediaSettings::GetInstance().GetCurrentGameSettings();
+
+ for (unsigned int i = 0; i < m_stretchModes.size(); i++)
+ {
+ const RETRO::STRETCHMODE stretchMode = m_stretchModes[i].stretchMode;
+ if (stretchMode == gameSettings.StretchMode())
+ return i;
+ }
+
+ return 0;
+}
+
+void CDialogGameStretchMode::PostExit()
+{
+ m_stretchModes.clear();
+}
+
+bool CDialogGameStretchMode::OnClickAction()
+{
+ Close();
+ return true;
+}
diff --git a/xbmc/games/dialogs/osd/DialogGameStretchMode.h b/xbmc/games/dialogs/osd/DialogGameStretchMode.h
new file mode 100644
index 0000000..1e05061
--- /dev/null
+++ b/xbmc/games/dialogs/osd/DialogGameStretchMode.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017-2018 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.
+ */
+
+#pragma once
+
+#include "DialogGameVideoSelect.h"
+#include "cores/GameSettings.h"
+
+#include <vector>
+
+namespace KODI
+{
+namespace GAME
+{
+class CDialogGameStretchMode : public CDialogGameVideoSelect
+{
+public:
+ CDialogGameStretchMode();
+ ~CDialogGameStretchMode() override = default;
+
+protected:
+ // implementation of CDialogGameVideoSelect
+ std::string GetHeading() override;
+ void PreInit() override;
+ void GetItems(CFileItemList& items) override;
+ void OnItemFocus(unsigned int index) override;
+ unsigned int GetFocusedItem() const override;
+ void PostExit() override;
+ bool OnClickAction() override;
+
+private:
+ struct StretchModeProperties
+ {
+ int stringIndex;
+ RETRO::STRETCHMODE stretchMode;
+ };
+
+ std::vector<StretchModeProperties> m_stretchModes;
+
+ /*!
+ * \brief The list of all the stretch modes along with their properties
+ */
+ static const std::vector<StretchModeProperties> m_allStretchModes;
+};
+} // namespace GAME
+} // namespace KODI
diff --git a/xbmc/games/dialogs/osd/DialogGameVideoFilter.cpp b/xbmc/games/dialogs/osd/DialogGameVideoFilter.cpp
new file mode 100644
index 0000000..a5bba0b
--- /dev/null
+++ b/xbmc/games/dialogs/osd/DialogGameVideoFilter.cpp
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017-2018 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 "DialogGameVideoFilter.h"
+
+#include "cores/RetroPlayer/guibridge/GUIGameVideoHandle.h"
+#include "cores/RetroPlayer/rendering/RenderVideoSettings.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/WindowIDs.h"
+#include "settings/GameSettings.h"
+#include "settings/MediaSettings.h"
+#include "utils/StringUtils.h"
+#include "utils/Variant.h"
+
+using namespace KODI;
+using namespace GAME;
+
+namespace
+{
+struct ScalingMethodProperties
+{
+ int nameIndex;
+ int categoryIndex;
+ int descriptionIndex;
+ RETRO::SCALINGMETHOD scalingMethod;
+};
+
+const std::vector<ScalingMethodProperties> scalingMethods = {
+ {16301, 16296, 16298, RETRO::SCALINGMETHOD::NEAREST},
+ {16302, 16297, 16299, RETRO::SCALINGMETHOD::LINEAR},
+};
+} // namespace
+
+CDialogGameVideoFilter::CDialogGameVideoFilter()
+ : CDialogGameVideoSelect(WINDOW_DIALOG_GAME_VIDEO_FILTER)
+{
+}
+
+std::string CDialogGameVideoFilter::GetHeading()
+{
+ return g_localizeStrings.Get(35225); // "Video filter"
+}
+
+void CDialogGameVideoFilter::PreInit()
+{
+ m_items.Clear();
+
+ InitVideoFilters();
+
+ if (m_items.Size() == 0)
+ {
+ CFileItemPtr item = std::make_shared<CFileItem>(g_localizeStrings.Get(231)); // "None"
+ m_items.Add(std::move(item));
+ }
+
+ m_bHasDescription = false;
+}
+
+void CDialogGameVideoFilter::InitVideoFilters()
+{
+ if (m_gameVideoHandle)
+ {
+ for (const auto& scalingMethodProps : scalingMethods)
+ {
+ if (m_gameVideoHandle->SupportsScalingMethod(scalingMethodProps.scalingMethod))
+ {
+ RETRO::CRenderVideoSettings videoSettings;
+ videoSettings.SetScalingMethod(scalingMethodProps.scalingMethod);
+
+ CFileItemPtr item =
+ std::make_shared<CFileItem>(g_localizeStrings.Get(scalingMethodProps.nameIndex));
+ item->SetLabel2(g_localizeStrings.Get(scalingMethodProps.categoryIndex));
+ item->SetProperty("game.videofilter", CVariant{videoSettings.GetVideoFilter()});
+ item->SetProperty("game.videofilterdescription",
+ CVariant{g_localizeStrings.Get(scalingMethodProps.descriptionIndex)});
+ m_items.Add(std::move(item));
+ }
+ }
+ }
+}
+
+void CDialogGameVideoFilter::GetItems(CFileItemList& items)
+{
+ for (const auto& item : m_items)
+ items.Add(item);
+}
+
+void CDialogGameVideoFilter::OnItemFocus(unsigned int index)
+{
+ if (static_cast<int>(index) < m_items.Size())
+ {
+ CFileItemPtr item = m_items[index];
+
+ std::string videoFilter;
+ std::string description;
+ GetProperties(*item, videoFilter, description);
+
+ CGameSettings& gameSettings = CMediaSettings::GetInstance().GetCurrentGameSettings();
+
+ if (gameSettings.VideoFilter() != videoFilter)
+ {
+ gameSettings.SetVideoFilter(videoFilter);
+ gameSettings.NotifyObservers(ObservableMessageSettingsChanged);
+
+ OnDescriptionChange(description);
+ m_bHasDescription = true;
+ }
+ else if (!m_bHasDescription)
+ {
+ OnDescriptionChange(description);
+ m_bHasDescription = true;
+ }
+ }
+}
+
+unsigned int CDialogGameVideoFilter::GetFocusedItem() const
+{
+ CGameSettings& gameSettings = CMediaSettings::GetInstance().GetCurrentGameSettings();
+
+ for (int i = 0; i < m_items.Size(); i++)
+ {
+ std::string videoFilter;
+ std::string description;
+ GetProperties(*m_items[i], videoFilter, description);
+
+ if (videoFilter == gameSettings.VideoFilter())
+ {
+ return i;
+ }
+ }
+
+ return 0;
+}
+
+void CDialogGameVideoFilter::PostExit()
+{
+ m_items.Clear();
+}
+
+bool CDialogGameVideoFilter::OnClickAction()
+{
+ Close();
+ return true;
+}
+
+void CDialogGameVideoFilter::GetProperties(const CFileItem& item,
+ std::string& videoFilter,
+ std::string& description)
+{
+ videoFilter = item.GetProperty("game.videofilter").asString();
+ description = item.GetProperty("game.videofilterdescription").asString();
+}
diff --git a/xbmc/games/dialogs/osd/DialogGameVideoFilter.h b/xbmc/games/dialogs/osd/DialogGameVideoFilter.h
new file mode 100644
index 0000000..7ec8c76
--- /dev/null
+++ b/xbmc/games/dialogs/osd/DialogGameVideoFilter.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017-2018 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.
+ */
+
+#pragma once
+
+#include "DialogGameVideoSelect.h"
+#include "FileItem.h"
+
+namespace KODI
+{
+namespace GAME
+{
+class CDialogGameVideoFilter : public CDialogGameVideoSelect
+{
+public:
+ CDialogGameVideoFilter();
+ ~CDialogGameVideoFilter() override = default;
+
+protected:
+ // implementation of CDialogGameVideoSelect
+ std::string GetHeading() override;
+ void PreInit() override;
+ void GetItems(CFileItemList& items) override;
+ void OnItemFocus(unsigned int index) override;
+ unsigned int GetFocusedItem() const override;
+ void PostExit() override;
+ bool OnClickAction() override;
+
+private:
+ void InitVideoFilters();
+
+ static void GetProperties(const CFileItem& item,
+ std::string& videoFilter,
+ std::string& description);
+
+ CFileItemList m_items;
+
+ //! \brief Set to true when a description has first been set
+ bool m_bHasDescription = false;
+};
+} // namespace GAME
+} // namespace KODI
diff --git a/xbmc/games/dialogs/osd/DialogGameVideoRotation.cpp b/xbmc/games/dialogs/osd/DialogGameVideoRotation.cpp
new file mode 100644
index 0000000..7037a6b
--- /dev/null
+++ b/xbmc/games/dialogs/osd/DialogGameVideoRotation.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2018 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 "DialogGameVideoRotation.h"
+
+#include "FileItem.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/WindowIDs.h"
+#include "settings/GameSettings.h"
+#include "settings/MediaSettings.h"
+#include "utils/Variant.h"
+
+using namespace KODI;
+using namespace GAME;
+
+CDialogGameVideoRotation::CDialogGameVideoRotation()
+ : CDialogGameVideoSelect(WINDOW_DIALOG_GAME_VIDEO_ROTATION)
+{
+}
+
+std::string CDialogGameVideoRotation::GetHeading()
+{
+ return g_localizeStrings.Get(35227); // "Rotation"
+}
+
+void CDialogGameVideoRotation::PreInit()
+{
+ m_rotations.clear();
+
+ // Present the user with clockwise rotation
+ m_rotations.push_back(0);
+ m_rotations.push_back(270);
+ m_rotations.push_back(180);
+ m_rotations.push_back(90);
+}
+
+void CDialogGameVideoRotation::GetItems(CFileItemList& items)
+{
+ for (unsigned int rotation : m_rotations)
+ {
+ CFileItemPtr item = std::make_shared<CFileItem>(GetRotationLabel(rotation));
+ item->SetProperty("game.videorotation", CVariant{rotation});
+ items.Add(std::move(item));
+ }
+}
+
+void CDialogGameVideoRotation::OnItemFocus(unsigned int index)
+{
+ if (index < m_rotations.size())
+ {
+ const unsigned int rotationDegCCW = m_rotations[index];
+
+ CGameSettings& gameSettings = CMediaSettings::GetInstance().GetCurrentGameSettings();
+ if (gameSettings.RotationDegCCW() != rotationDegCCW)
+ {
+ gameSettings.SetRotationDegCCW(rotationDegCCW);
+ gameSettings.NotifyObservers(ObservableMessageSettingsChanged);
+ }
+ }
+}
+
+unsigned int CDialogGameVideoRotation::GetFocusedItem() const
+{
+ CGameSettings& gameSettings = CMediaSettings::GetInstance().GetCurrentGameSettings();
+
+ for (unsigned int i = 0; i < m_rotations.size(); i++)
+ {
+ const unsigned int rotationDegCCW = m_rotations[i];
+ if (rotationDegCCW == gameSettings.RotationDegCCW())
+ return i;
+ }
+
+ return 0;
+}
+
+void CDialogGameVideoRotation::PostExit()
+{
+ m_rotations.clear();
+}
+
+bool CDialogGameVideoRotation::OnClickAction()
+{
+ Close();
+ return true;
+}
+
+std::string CDialogGameVideoRotation::GetRotationLabel(unsigned int rotationDegCCW)
+{
+ switch (rotationDegCCW)
+ {
+ case 0:
+ return g_localizeStrings.Get(35228); // 0
+ case 90:
+ return g_localizeStrings.Get(35231); // 270
+ case 180:
+ return g_localizeStrings.Get(35230); // 180
+ case 270:
+ return g_localizeStrings.Get(35229); // 90
+ default:
+ break;
+ }
+
+ return "";
+}
diff --git a/xbmc/games/dialogs/osd/DialogGameVideoRotation.h b/xbmc/games/dialogs/osd/DialogGameVideoRotation.h
new file mode 100644
index 0000000..ef454db
--- /dev/null
+++ b/xbmc/games/dialogs/osd/DialogGameVideoRotation.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#pragma once
+
+#include "DialogGameVideoSelect.h"
+
+#include <string>
+#include <vector>
+
+namespace KODI
+{
+namespace GAME
+{
+class CDialogGameVideoRotation : public CDialogGameVideoSelect
+{
+public:
+ CDialogGameVideoRotation();
+ ~CDialogGameVideoRotation() override = default;
+
+protected:
+ // implementation of CDialogGameVideoSelect
+ std::string GetHeading() override;
+ void PreInit() override;
+ void GetItems(CFileItemList& items) override;
+ void OnItemFocus(unsigned int index) override;
+ unsigned int GetFocusedItem() const override;
+ void PostExit() override;
+ bool OnClickAction() override;
+
+private:
+ // Helper functions
+ static std::string GetRotationLabel(unsigned int rotationDegCCW);
+
+ // Dialog parameters
+ std::vector<unsigned int> m_rotations; // Degrees counter-clockwise
+};
+} // namespace GAME
+} // namespace KODI
diff --git a/xbmc/games/dialogs/osd/DialogGameVideoSelect.cpp b/xbmc/games/dialogs/osd/DialogGameVideoSelect.cpp
new file mode 100644
index 0000000..a69b26f
--- /dev/null
+++ b/xbmc/games/dialogs/osd/DialogGameVideoSelect.cpp
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2017-2018 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 "DialogGameVideoSelect.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "cores/RetroPlayer/guibridge/GUIGameRenderManager.h"
+#include "cores/RetroPlayer/guibridge/GUIGameVideoHandle.h"
+#include "games/dialogs/DialogGameDefines.h"
+#include "guilib/GUIBaseContainer.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIMessage.h"
+#include "guilib/GUIWindowManager.h"
+#include "input/actions/ActionIDs.h"
+#include "settings/GameSettings.h"
+#include "settings/MediaSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "view/GUIViewControl.h"
+#include "view/ViewState.h"
+#include "windowing/GraphicContext.h"
+
+using namespace KODI;
+using namespace GAME;
+
+CDialogGameVideoSelect::CDialogGameVideoSelect(int windowId)
+ : CGUIDialog(windowId, "DialogSelect.xml"),
+ m_viewControl(new CGUIViewControl),
+ m_vecItems(new CFileItemList)
+{
+ // Initialize CGUIWindow
+ m_loadType = KEEP_IN_MEMORY;
+}
+
+CDialogGameVideoSelect::~CDialogGameVideoSelect() = default;
+
+bool CDialogGameVideoSelect::OnMessage(CGUIMessage& message)
+{
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_WINDOW_INIT:
+ {
+ RegisterDialog();
+
+ // Don't init this dialog if we aren't playing a game
+ if (!m_gameVideoHandle || !m_gameVideoHandle->IsPlayingGame())
+ return false;
+
+ break;
+ }
+ case GUI_MSG_WINDOW_DEINIT:
+ {
+ UnregisterDialog();
+
+ break;
+ }
+ case GUI_MSG_SETFOCUS:
+ {
+ const int controlId = message.GetControlId();
+ if (m_viewControl->HasControl(controlId) && m_viewControl->GetCurrentControl() != controlId)
+ {
+ m_viewControl->SetFocused();
+ return true;
+ }
+ break;
+ }
+ case GUI_MSG_CLICKED:
+ {
+ const int actionId = message.GetParam1();
+ if (actionId == ACTION_SELECT_ITEM || actionId == ACTION_MOUSE_LEFT_CLICK)
+ {
+ const int controlId = message.GetSenderId();
+ if (m_viewControl->HasControl(controlId))
+ {
+ if (OnClickAction())
+ return true;
+ }
+ }
+ else if (actionId == ACTION_CONTEXT_MENU || actionId == ACTION_MOUSE_RIGHT_CLICK)
+ {
+ const int controlId = message.GetSenderId();
+ if (m_viewControl->HasControl(controlId))
+ {
+ if (OnMenuAction())
+ return true;
+ }
+ }
+ else if (actionId == ACTION_CREATE_BOOKMARK)
+ {
+ const int controlId = message.GetSenderId();
+ if (m_viewControl->HasControl(controlId))
+ {
+ if (OnOverwriteAction())
+ return true;
+ }
+ }
+ else if (actionId == ACTION_RENAME_ITEM)
+ {
+ const int controlId = message.GetSenderId();
+ if (m_viewControl->HasControl(controlId))
+ {
+ if (OnRenameAction())
+ return true;
+ }
+ }
+ else if (actionId == ACTION_DELETE_ITEM)
+ {
+ const int controlId = message.GetSenderId();
+ if (m_viewControl->HasControl(controlId))
+ {
+ if (OnDeleteAction())
+ return true;
+ }
+ }
+
+ break;
+ }
+ case GUI_MSG_REFRESH_LIST:
+ {
+ RefreshList();
+ break;
+ }
+ default:
+ break;
+ }
+
+ return CGUIDialog::OnMessage(message);
+}
+
+void CDialogGameVideoSelect::FrameMove()
+{
+ CGUIBaseContainer* thumbs = dynamic_cast<CGUIBaseContainer*>(GetControl(CONTROL_VIDEO_THUMBS));
+ if (thumbs != nullptr)
+ OnItemFocus(thumbs->GetSelectedItem());
+
+ CGUIDialog::FrameMove();
+}
+
+void CDialogGameVideoSelect::OnWindowLoaded()
+{
+ CGUIDialog::OnWindowLoaded();
+
+ m_viewControl->SetParentWindow(GetID());
+ m_viewControl->AddView(GetControl(CONTROL_VIDEO_THUMBS));
+}
+
+void CDialogGameVideoSelect::OnWindowUnload()
+{
+ m_viewControl->Reset();
+
+ CGUIDialog::OnWindowUnload();
+}
+
+void CDialogGameVideoSelect::OnInitWindow()
+{
+ PreInit();
+
+ CGUIDialog::OnInitWindow();
+
+ Update();
+
+ CGUIMessage msg(GUI_MSG_SETFOCUS, GetID(), CONTROL_VIDEO_THUMBS);
+ OnMessage(msg);
+
+ std::string heading = GetHeading();
+ SET_CONTROL_LABEL(CONTROL_VIDEO_HEADING, heading);
+
+ FrameMove();
+}
+
+void CDialogGameVideoSelect::OnDeinitWindow(int nextWindowID)
+{
+ Clear();
+
+ CGUIDialog::OnDeinitWindow(nextWindowID);
+
+ PostExit();
+
+ SaveSettings();
+}
+
+void CDialogGameVideoSelect::Update()
+{
+ //! @todo
+ // Lock our display, as this window is rendered from the player thread
+ // CServiceBroker::GetWinSystem()->GetGfxContext().Lock();
+
+ m_viewControl->SetCurrentView(DEFAULT_VIEW_ICONS);
+
+ // Empty the list ready for population
+ Clear();
+
+ RefreshList();
+
+ // CServiceBroker::GetWinSystem()->GetGfxContext().Unlock();
+}
+
+void CDialogGameVideoSelect::Clear()
+{
+ m_viewControl->Clear();
+ m_vecItems->Clear();
+}
+
+void CDialogGameVideoSelect::RefreshList()
+{
+ m_vecItems->Clear();
+
+ GetItems(*m_vecItems);
+
+ m_viewControl->SetItems(*m_vecItems);
+
+ auto focusedIndex = GetFocusedItem();
+ m_viewControl->SetSelectedItem(focusedIndex);
+ OnItemFocus(focusedIndex);
+
+ // Refresh the panel container
+ CGUIMessage message(GUI_MSG_REFRESH_THUMBS, GetID(), CONTROL_VIDEO_THUMBS);
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message, GetID());
+}
+
+void CDialogGameVideoSelect::SaveSettings()
+{
+ CGameSettings& defaultSettings = CMediaSettings::GetInstance().GetDefaultGameSettings();
+ CGameSettings& currentSettings = CMediaSettings::GetInstance().GetCurrentGameSettings();
+
+ if (defaultSettings != currentSettings)
+ {
+ defaultSettings = currentSettings;
+ CServiceBroker::GetSettingsComponent()->GetSettings()->Save();
+ }
+}
+
+void CDialogGameVideoSelect::OnDescriptionChange(const std::string& description)
+{
+ CGUIMessage msg(GUI_MSG_LABEL_SET, GetID(), CONTROL_VIDEO_THUMBS);
+ msg.SetLabel(description);
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, GetID());
+}
+
+void CDialogGameVideoSelect::RegisterDialog()
+{
+ m_gameVideoHandle = CServiceBroker::GetGameRenderManager().RegisterDialog(*this);
+}
+
+void CDialogGameVideoSelect::UnregisterDialog()
+{
+ m_gameVideoHandle.reset();
+}
diff --git a/xbmc/games/dialogs/osd/DialogGameVideoSelect.h b/xbmc/games/dialogs/osd/DialogGameVideoSelect.h
new file mode 100644
index 0000000..c14c94e
--- /dev/null
+++ b/xbmc/games/dialogs/osd/DialogGameVideoSelect.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017-2018 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.
+ */
+
+#pragma once
+
+#include "guilib/GUIDialog.h"
+
+#include <memory>
+
+class CFileItemList;
+class CGUIViewControl;
+
+namespace KODI
+{
+namespace RETRO
+{
+class CGUIGameVideoHandle;
+}
+
+namespace GAME
+{
+class CDialogGameVideoSelect : public CGUIDialog
+{
+public:
+ ~CDialogGameVideoSelect() override;
+
+ // implementation of CGUIControl via CGUIDialog
+ bool OnMessage(CGUIMessage& message) override;
+
+ // implementation of CGUIWindow via CGUIDialog
+ void FrameMove() override;
+ void OnDeinitWindow(int nextWindowID) override;
+
+protected:
+ CDialogGameVideoSelect(int windowId);
+
+ // implementation of CGUIWindow via CGUIDialog
+ void OnWindowUnload() override;
+ void OnWindowLoaded() override;
+ void OnInitWindow() override;
+
+ // Video select interface
+ virtual std::string GetHeading() = 0;
+ virtual void PreInit() = 0;
+ virtual void GetItems(CFileItemList& items) = 0;
+ virtual void OnItemFocus(unsigned int index) = 0;
+ virtual unsigned int GetFocusedItem() const = 0;
+ virtual void PostExit() = 0;
+ // override this to do something when an item is selected
+ virtual bool OnClickAction() { return false; }
+ // override this to do something when an item's context menu is opened
+ virtual bool OnMenuAction() { return false; }
+ // override this to do something when an item is overwritten with a new savestate
+ virtual bool OnOverwriteAction() { return false; }
+ // override this to do something when an item is renamed
+ virtual bool OnRenameAction() { return false; }
+ // override this to do something when an item is deleted
+ virtual bool OnDeleteAction() { return false; }
+
+ // GUI functions
+ void RefreshList();
+ void OnDescriptionChange(const std::string& description);
+
+ std::shared_ptr<RETRO::CGUIGameVideoHandle> m_gameVideoHandle;
+
+private:
+ void Update();
+ void Clear();
+
+ void SaveSettings();
+
+ void RegisterDialog();
+ void UnregisterDialog();
+
+ std::unique_ptr<CGUIViewControl> m_viewControl;
+ std::unique_ptr<CFileItemList> m_vecItems;
+};
+} // namespace GAME
+} // namespace KODI
diff --git a/xbmc/games/dialogs/osd/DialogGameVolume.cpp b/xbmc/games/dialogs/osd/DialogGameVolume.cpp
new file mode 100644
index 0000000..8467269
--- /dev/null
+++ b/xbmc/games/dialogs/osd/DialogGameVolume.cpp
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2017-2018 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 "DialogGameVolume.h"
+
+#include "ServiceBroker.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationVolumeHandling.h"
+#include "dialogs/GUIDialogVolumeBar.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIDialog.h"
+#include "guilib/GUIMessage.h"
+#include "guilib/GUISliderControl.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/WindowIDs.h"
+#include "interfaces/AnnouncementManager.h"
+#include "utils/Variant.h"
+
+#include <cmath>
+
+using namespace KODI;
+using namespace GAME;
+
+#define CONTROL_LABEL 12 //! @todo Remove me
+
+CDialogGameVolume::CDialogGameVolume()
+{
+ // Initialize CGUIWindow
+ SetID(WINDOW_DIALOG_GAME_VOLUME);
+ m_loadType = KEEP_IN_MEMORY;
+}
+
+bool CDialogGameVolume::OnMessage(CGUIMessage& message)
+{
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_STATE_CHANGED:
+ {
+ int controlId = message.GetControlId();
+ if (controlId == GetID())
+ {
+ OnStateChanged();
+ return true;
+ }
+
+ break;
+ }
+ default:
+ break;
+ }
+
+ return CGUIDialogSlider::OnMessage(message);
+}
+
+void CDialogGameVolume::OnInitWindow()
+{
+ m_volumePercent = m_oldVolumePercent = GetVolumePercent();
+
+ CGUIDialogSlider::OnInitWindow();
+
+ // Set slider parameters
+ SetModalityType(DialogModalityType::MODAL);
+ SetSlider(GetLabel(), GetVolumePercent(), VOLUME_MIN, VOLUME_DELTA, VOLUME_MAX, this, nullptr);
+
+ SET_CONTROL_HIDDEN(CONTROL_LABEL);
+
+ CGUIDialogVolumeBar* dialogVolumeBar = dynamic_cast<CGUIDialogVolumeBar*>(
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_VOLUME_BAR));
+ if (dialogVolumeBar != nullptr)
+ dialogVolumeBar->RegisterCallback(this);
+
+ CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this);
+}
+
+void CDialogGameVolume::OnDeinitWindow(int nextWindowID)
+{
+ CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this);
+
+ CGUIDialogVolumeBar* dialogVolumeBar = dynamic_cast<CGUIDialogVolumeBar*>(
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_VOLUME_BAR));
+ if (dialogVolumeBar != nullptr)
+ dialogVolumeBar->UnregisterCallback(this);
+
+ CGUIDialogSlider::OnDeinitWindow(nextWindowID);
+}
+
+void CDialogGameVolume::OnSliderChange(void* data, CGUISliderControl* slider)
+{
+ const float volumePercent = slider->GetFloatValue();
+
+ if (std::fabs(volumePercent - m_volumePercent) > 0.1f)
+ {
+ m_volumePercent = volumePercent;
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appVolume = components.GetComponent<CApplicationVolumeHandling>();
+ appVolume->SetVolume(volumePercent, true);
+ }
+}
+
+bool CDialogGameVolume::IsShown() const
+{
+ return m_active;
+}
+
+void CDialogGameVolume::Announce(ANNOUNCEMENT::AnnouncementFlag flag,
+ const std::string& sender,
+ const std::string& message,
+ const CVariant& data)
+{
+ if (flag == ANNOUNCEMENT::Application && message == "OnVolumeChanged")
+ {
+ const float volumePercent = static_cast<float>(data["volume"].asDouble());
+
+ if (std::fabs(volumePercent - m_volumePercent) > 0.1f)
+ {
+ m_volumePercent = volumePercent;
+
+ CGUIMessage msg(GUI_MSG_STATE_CHANGED, GetID(), GetID());
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
+ }
+ }
+}
+
+void CDialogGameVolume::OnStateChanged()
+{
+ if (m_volumePercent != m_oldVolumePercent)
+ {
+ m_oldVolumePercent = m_volumePercent;
+ SetSlider(GetLabel(), m_volumePercent, VOLUME_MIN, VOLUME_DELTA, VOLUME_MAX, this, nullptr);
+ }
+}
+
+float CDialogGameVolume::GetVolumePercent() const
+{
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appVolume = components.GetComponent<CApplicationVolumeHandling>();
+ return appVolume->GetVolumePercent();
+}
+
+std::string CDialogGameVolume::GetLabel()
+{
+ return g_localizeStrings.Get(13376); // "Volume"
+}
diff --git a/xbmc/games/dialogs/osd/DialogGameVolume.h b/xbmc/games/dialogs/osd/DialogGameVolume.h
new file mode 100644
index 0000000..a32339d
--- /dev/null
+++ b/xbmc/games/dialogs/osd/DialogGameVolume.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017-2018 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.
+ */
+
+#pragma once
+
+#include "dialogs/GUIDialogSlider.h"
+#include "dialogs/IGUIVolumeBarCallback.h"
+#include "guilib/ISliderCallback.h"
+#include "interfaces/IAnnouncer.h"
+
+#include <set>
+
+namespace KODI
+{
+
+namespace GAME
+{
+class CDialogGameVolume : public CGUIDialogSlider, // GUI interface
+ public ISliderCallback, // GUI callback
+ public IGUIVolumeBarCallback, // Volume bar dialog callback
+ public ANNOUNCEMENT::IAnnouncer // Application callback
+{
+public:
+ CDialogGameVolume();
+ ~CDialogGameVolume() override = default;
+
+ // implementation of CGUIControl via CGUIDialogSlider
+ bool OnMessage(CGUIMessage& message) override;
+
+ // implementation of CGUIWindow via CGUIDialogSlider
+ void OnDeinitWindow(int nextWindowID) override;
+
+ // implementation of ISliderCallback
+ void OnSliderChange(void* data, CGUISliderControl* slider) override;
+
+ // implementation of IGUIVolumeBarCallback
+ bool IsShown() const override;
+
+ // implementation of IAnnouncer
+ void Announce(ANNOUNCEMENT::AnnouncementFlag flag,
+ const std::string& sender,
+ const std::string& message,
+ const CVariant& data) override;
+
+protected:
+ // implementation of CGUIWindow via CGUIDialogSlider
+ void OnInitWindow() override;
+
+private:
+ /*!
+ * \brief Call when state change message is received
+ */
+ void OnStateChanged();
+
+ /*!
+ * \brief Get the volume of the first callback
+ *
+ * \return The volume, as a fraction of maximum volume
+ */
+ float GetVolumePercent() const;
+
+ /*!
+ * \brief Get the volume bar label
+ */
+ static std::string GetLabel();
+
+ // Volume parameters
+ const float VOLUME_MIN = 0.0f;
+ const float VOLUME_DELTA = 10.0f;
+ const float VOLUME_MAX = 100.0f;
+ float m_volumePercent = 100.0f;
+ float m_oldVolumePercent = 100.0f;
+};
+} // namespace GAME
+} // namespace KODI
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;
+}
diff --git a/xbmc/games/dialogs/osd/DialogInGameSaves.h b/xbmc/games/dialogs/osd/DialogInGameSaves.h
new file mode 100644
index 0000000..6f98f94
--- /dev/null
+++ b/xbmc/games/dialogs/osd/DialogInGameSaves.h
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "DialogGameVideoSelect.h"
+#include "FileItem.h"
+#include "guilib/GUIListItem.h"
+
+#include <string>
+
+namespace KODI
+{
+namespace GAME
+{
+class CDialogInGameSaves : public CDialogGameVideoSelect
+{
+public:
+ CDialogInGameSaves();
+ ~CDialogInGameSaves() override = default;
+
+ // implementation of CGUIControl via CDialogGameVideoSelect
+ bool OnMessage(CGUIMessage& message) override;
+
+protected:
+ // implementation of CDialogGameVideoSelect
+ std::string GetHeading() override;
+ void PreInit() override;
+ void GetItems(CFileItemList& items) override;
+ void OnItemFocus(unsigned int index) override;
+ unsigned int GetFocusedItem() const override;
+ void PostExit() override;
+ bool OnClickAction() override;
+ bool OnMenuAction() override;
+ bool OnOverwriteAction() override;
+ bool OnRenameAction() override;
+ bool OnDeleteAction() override;
+
+ void OnNewSave();
+ void OnLoad(CFileItem& focusedItem);
+ void OnOverwrite(CFileItem& focusedItem);
+ void OnRename(CFileItem& focusedItem);
+ void OnDelete(CFileItem& focusedItem);
+
+private:
+ void InitSavedGames();
+ void OnItemRefresh(const std::string& itemPath, CGUIListItemPtr itemInfo);
+
+ /*!
+ * \brief Translates the GUI list item received in a GUI message into a
+ * CFileItem with savestate properties
+ *
+ * When a savestate is overwritten, we optimistically populate the GUI list
+ * with a simulated savestate for immediate user feedback. Later (about a
+ * quarter second) a message arrives with the real savestate info.
+ *
+ * \param messagePath The savestate path, pass as the message's string param
+ * \param messageItem The savestate info, if known, or empty if unknown
+ *
+ * If messageItem is empty, the savestate will be loaded from disk, which
+ * is potentially expensive.
+ *
+ * \return A savestate item for the GUI, or empty if no savestate information
+ * can be obtained
+ */
+ static CFileItemPtr TranslateMessageItem(const std::string& messagePath,
+ CGUIListItemPtr messageItem);
+
+ CFileItemList m_savestateItems;
+ const CFileItemPtr m_newSaveItem;
+ unsigned int m_focusedItemIndex = false;
+};
+} // namespace GAME
+} // namespace KODI