pcsx2/pcsx2/gui/MainFrame.cpp

878 lines
35 KiB
C++

/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2021 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "MainFrame.h"
#include "AppSaveStates.h"
#include "ConsoleLogger.h"
#include "MSWstuff.h"
#include "Dialogs/ModalPopups.h"
#include "IsoDropTarget.h"
#include "fmt/core.h"
#include <wx/iconbndl.h>
#include <unordered_map>
#include "AppAccelerators.h"
#include "svnrev.h"
#include "Saveslots.h"
#ifndef DISABLE_RECORDING
#include "Recording/InputRecording.h"
#include "Recording/InputRecordingControls.h"
#endif
extern std::atomic_bool init_gspanel;
// ------------------------------------------------------------------------
wxMenu* MainEmuFrame::MakeStatesSubMenu(int baseid, int loadBackupId) const
{
wxMenu* mnuSubstates = new wxMenu();
for (int i = 0; i < 10; i++)
{
// Will be changed once an iso is loaded.
wxMenuItem* m = mnuSubstates->Append(baseid + i + 1, wxsFormat(_("Slot %d"), i), wxEmptyString, wxITEM_CHECK);
m->Check(i == 0); // 0 is the default selected slot
}
if (loadBackupId >= 0)
{
mnuSubstates->AppendSeparator();
wxMenuItem* m = mnuSubstates->Append(loadBackupId, _("Backup"));
m->Enable(false);
}
// Implement custom hotkeys (F2) + (Shift + F2) with translatable string intact + not blank in GUI.
// baseid in the negatives will order in a different section, so if you want to increase more slots you can still easily do this, as -1 it will have the same function as opening file for savestates, which is bad
// For safety i also made them inactive aka grayed out to signify that's it's only for informational purposes
// Fixme: In the future this can still be expanded to actually cycle savestates in the GUI.
mnuSubstates->Append(baseid - 1, _("File..."));
wxMenuItem* CycleNext = mnuSubstates->Append(baseid - 2, _("Cycle to next slot") + wxString(" ") + fmt::format("({})", wxGetApp().GlobalAccels->findKeycodeWithCommandId("States_CycleSlotForward").toTitleizedString()));
CycleNext->Enable(false);
wxMenuItem* CycleBack = mnuSubstates->Append(baseid - 3, _("Cycle to previous slot") + wxString(" ") + fmt::format("({})", wxGetApp().GlobalAccels->findKeycodeWithCommandId("States_CycleSlotBackward").toTitleizedString()));
CycleBack->Enable(false);
return mnuSubstates;
}
void MainEmuFrame::UpdateStatusBar()
{
wxString temp(wxEmptyString);
#ifndef DISABLE_RECORDING
if (g_InputRecording.IsActive() && g_InputRecording.GetInputRecordingData().FromSaveState())
temp += "Base Savestate - " + g_InputRecording.GetInputRecordingData().GetFilename() + "_SaveState.p2s";
else
#endif
{
if (g_Conf->EnableFastBoot)
temp += "Fast Boot - ";
if (g_Conf->CdvdSource == CDVD_SourceType::Iso)
temp += "Load: '" + wxFileName(g_Conf->CurrentIso).GetFullName() + "' ";
}
m_statusbar.SetStatusText(temp, 0);
m_statusbar.SetStatusText(CDVD_SourceLabels[enum_cast(g_Conf->CdvdSource)], 1);
#ifdef __M_X86_64
m_statusbar.SetStatusText("x64", 2);
#else
m_statusbar.SetStatusText("x32", 2);
#endif
}
void MainEmuFrame::UpdateCdvdSrcSelection()
{
MenuIdentifiers cdsrc = MenuId_Src_Iso;
switch (g_Conf->CdvdSource)
{
case CDVD_SourceType::Iso:
cdsrc = MenuId_Src_Iso;
break;
case CDVD_SourceType::Disc:
cdsrc = MenuId_Src_Disc;
break;
case CDVD_SourceType::NoDisc:
cdsrc = MenuId_Src_NoDisc;
break;
jNO_DEFAULT
}
sMenuBar.Check(cdsrc, true);
#ifndef DISABLE_RECORDING
if (!g_InputRecording.IsActive())
#endif
ApplyCDVDStatus();
UpdateStatusBar();
}
bool MainEmuFrame::Destroy()
{
// Sigh: wxWidgets doesn't issue Destroy() calls for children windows when the parent
// is destroyed (it just deletes them, quite suddenly). So let's do it for them, since
// our children have configuration stuff they like to do when they're closing.
for (
wxWindowList::const_iterator
i = wxTopLevelWindows.begin(),
end = wxTopLevelWindows.end();
i != end; ++i)
{
wxTopLevelWindow* const win = wx_static_cast(wxTopLevelWindow*, *i);
if (win == this)
continue;
if (win->GetParent() != this)
continue;
win->Destroy();
}
return _parent::Destroy();
}
// ------------------------------------------------------------------------
// MainFrame OnEvent Handlers
// ------------------------------------------------------------------------
// Close out the console log windows along with the main emu window.
// Note: This event only happens after a close event has occurred and was *not* veto'd. Ie,
// it means it's time to provide an unconditional closure of said window.
//
void MainEmuFrame::OnCloseWindow(wxCloseEvent& evt)
{
// the main thread is busy suspending everything, so let's not try to call it
// when closing the emulator
init_gspanel = false;
if (IsBeingDeleted())
return;
CoreThread.Suspend();
//bool isClosing = false;
if (!evt.CanVeto())
{
// Mandatory destruction...
//isClosing = true;
}
else
{
// TODO : Add confirmation prior to exit here!
// Problem: Suspend is often slow because it needs to wait until the current EE frame
// has finished processing (if the GS or logging has incurred severe overhead this makes
// closing PCSX2 difficult). A non-blocking suspend with modal dialog might suffice
// however. --air
//evt.Veto( true );
}
sApp.OnMainFrameClosed(GetId());
RemoveCdvdMenu();
RemoveEventHandler(&wxGetApp().GetRecentIsoManager());
wxGetApp().PostIdleAppMethod(&Pcsx2App::PrepForExit);
evt.Skip();
}
void MainEmuFrame::OnMoveAround(wxMoveEvent& evt)
{
if (IsBeingDeleted() || !IsVisible() || IsIconized())
return;
// Uncomment this when doing logger stress testing (and then move the window around
// while the logger spams itself)
// ... makes for a good test of the message pump's responsiveness.
if (EnableThreadedLoggingTest)
Console.Warning("Threaded Logging Test! (a window move event)");
// evt.GetPosition() returns the client area position, not the window frame position.
// So read the window's screen-relative position directly.
g_Conf->MainGuiPosition = GetScreenPosition();
// wxGTK note: X sends gratuitous amounts of OnMove messages for various crap actions
// like selecting or deselecting a window, which muck up docking logic. We filter them
// out using 'lastpos' here. :)
static wxPoint lastpos(wxDefaultCoord, wxDefaultCoord);
if (lastpos == evt.GetPosition())
return;
lastpos = evt.GetPosition();
if (g_Conf->ProgLogBox.AutoDock)
{
if (ConsoleLogFrame* proglog = wxGetApp().GetProgramLog())
{
if (!proglog->IsMaximized())
{
g_Conf->ProgLogBox.DisplayPosition = GetRect().GetTopRight();
proglog->SetPosition(g_Conf->ProgLogBox.DisplayPosition);
}
}
}
evt.Skip();
}
void MainEmuFrame::OnLogBoxHidden()
{
g_Conf->ProgLogBox.Visible = false;
m_MenuItem_Console.Check(false);
}
// ------------------------------------------------------------------------
void MainEmuFrame::ConnectMenus()
{
// System
Bind(wxEVT_MENU, &MainEmuFrame::Menu_BootCdvd_Click, this, MenuId_Boot_CDVD);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_FastBoot_Click, this, MenuId_Config_FastBoot);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_OpenELF_Click, this, MenuId_Boot_ELF);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_SuspendResume_Click, this, MenuId_Sys_SuspendResume);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_LoadStates_Click, this, MenuId_State_Load01 + 1, MenuId_State_Load01 + 10);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_LoadStates_Click, this, MenuId_State_LoadBackup);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_LoadStateFromFile_Click, this, MenuId_State_LoadFromFile);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_SaveStates_Click, this, MenuId_State_Save01 + 1, MenuId_State_Save01 + 10);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_SaveStateToFile_Click, this, MenuId_State_SaveToFile);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_EnableBackupStates_Click, this, MenuId_EnableBackupStates);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_EnablePatches_Click, this, MenuId_EnablePatches);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_EnableCheats_Click, this, MenuId_EnableCheats);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_IPC_Enable_Click, this, MenuId_IPC_Enable);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_IPC_Settings_Click, this, MenuId_IPC_Settings);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_EnableWideScreenPatches_Click, this, MenuId_EnableWideScreenPatches);
#ifndef DISABLE_RECORDING
Bind(wxEVT_MENU, &MainEmuFrame::Menu_EnableRecordingTools_Click, this, MenuId_EnableInputRecording);
#endif
Bind(wxEVT_MENU, &MainEmuFrame::Menu_EnableHostFs_Click, this, MenuId_EnableHostFs);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_SysShutdown_Click, this, MenuId_Sys_Shutdown);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_Exit_Click, this, MenuId_Exit);
// CDVD
Bind(wxEVT_MENU, &MainEmuFrame::Menu_IsoBrowse_Click, this, MenuId_IsoBrowse);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_IsoClear_Click, this, MenuId_IsoClear);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_CdvdSource_Click, this, MenuId_Src_Iso);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_CdvdSource_Click, this, MenuId_Src_Disc);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_CdvdSource_Click, this, MenuId_Src_NoDisc);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_Ask_On_Boot_Click, this, MenuId_Ask_On_Booting);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_Debug_CreateBlockdump_Click, this, MenuId_Debug_CreateBlockdump);
// Config
Bind(wxEVT_MENU, &MainEmuFrame::Menu_SysSettings_Click, this, MenuId_Config_SysSettings);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_McdSettings_Click, this, MenuId_Config_McdSettings);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_SelectBios_Click, this, MenuId_Config_BIOS);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_AudioSettings_Click, this, MenuId_Config_SPU2);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_NetworkSettings_Click, this, MenuId_Config_DEV9);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_USBSettings_Click, this, MenuId_Config_USB);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_PADSettings_Click, this, MenuId_Config_PAD);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_GSSettings_Click, this, MenuId_Config_GS);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_GSSettings_Click, this, MenuId_Video_CoreSettings);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_WindowSettings_Click, this, MenuId_Video_WindowSettings);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_MultitapToggle_Click, this, MenuId_Config_Multitap0Toggle);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_MultitapToggle_Click, this, MenuId_Config_Multitap1Toggle);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_ResetAllSettings_Click, this, MenuId_Config_ResetAll);
// Misc
Bind(wxEVT_MENU, &MainEmuFrame::Menu_ShowConsole, this, MenuId_Console);
#if defined(__unix__)
Bind(wxEVT_MENU, &MainEmuFrame::Menu_ShowConsole_Stdio, this, MenuId_Console_Stdio);
#endif
Bind(wxEVT_MENU, &MainEmuFrame::Menu_GetStarted, this, MenuId_Help_GetStarted);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_Compatibility, this, MenuId_Help_Compatibility);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_Forums, this, MenuId_Help_Forums);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_Website, this, MenuId_Help_Website);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_Github, this, MenuId_Help_Github);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_Wiki, this, MenuId_Help_Wiki);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_ShowAboutBox, this, MenuId_About);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_ShowGSDump, this, MenuId_GSDump);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_ChangeLang, this, MenuId_ChangeLang);
// Debug
Bind(wxEVT_MENU, &MainEmuFrame::Menu_Debug_Open_Click, this, MenuId_Debug_Open);
// Capture
Bind(wxEVT_MENU, &MainEmuFrame::Menu_Capture_Video_ToggleCapture_Click, this, MenuId_Capture_Video_Record);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_Capture_Video_ToggleCapture_Click, this, MenuId_Capture_Video_Stop);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_Capture_Video_IncludeAudio_Click, this, MenuId_Capture_Video_IncludeAudio);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_Capture_Screenshot_Screenshot_Click, this, MenuId_Capture_Screenshot_Screenshot);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_Capture_Screenshot_Screenshot_As_Click, this, MenuId_Capture_Screenshot_Screenshot_As);
#ifndef DISABLE_RECORDING
// Recording
Bind(wxEVT_MENU, &MainEmuFrame::Menu_Recording_New_Click, this, MenuId_Recording_New);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_Recording_Play_Click, this, MenuId_Recording_Play);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_Recording_Stop_Click, this, MenuId_Recording_Stop);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_Recording_Config_FrameAdvance, this, MenuId_Recording_Config_FrameAdvance);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_Recording_TogglePause_Click, this, MenuId_Recording_TogglePause);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_Recording_FrameAdvance_Click, this, MenuId_Recording_FrameAdvance);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_Recording_ToggleRecordingMode_Click, this, MenuId_Recording_ToggleRecordingMode);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_Recording_VirtualPad_Open_Click, this, MenuId_Recording_VirtualPad_Port0);
Bind(wxEVT_MENU, &MainEmuFrame::Menu_Recording_VirtualPad_Open_Click, this, MenuId_Recording_VirtualPad_Port1);
#endif
}
void MainEmuFrame::InitLogBoxPosition(AppConfig::ConsoleLogOptions& conf)
{
conf.DisplaySize.Set(
std::min(std::max(conf.DisplaySize.GetWidth(), 160), wxGetDisplayArea().GetWidth()),
std::min(std::max(conf.DisplaySize.GetHeight(), 160), wxGetDisplayArea().GetHeight()));
if (conf.AutoDock)
{
conf.DisplayPosition = GetScreenPosition() + wxSize(GetSize().x, 0);
}
else if (conf.DisplayPosition != wxDefaultPosition)
{
if (!wxGetDisplayArea().Contains(wxRect(conf.DisplayPosition, conf.DisplaySize)))
conf.DisplayPosition = wxDefaultPosition;
}
}
void MainEmuFrame::DispatchEvent(const CoreThreadStatus& status)
{
if (!pxAssertMsg(GetMenuBar() != NULL, "Mainframe menu bar is NULL!"))
return;
ApplySuspendStatus();
}
void MainEmuFrame::AppStatusEvent_OnSettingsApplied()
{
ApplySettings();
}
void MainEmuFrame::CreatePcsx2Menu()
{
// ------------------------------------------------------------------------
// Some of the items in the System menu are configured by the UpdateCoreStatus() method.
m_menuSys.Append(MenuId_Boot_CDVD, _("Initializing..."));
m_menuSys.Append(MenuId_Sys_SuspendResume, _("Initializing..."));
m_menuSys.Append(MenuId_Sys_Shutdown, _("Shut&down"),
_("Wipes all internal VM states."));
m_menuSys.FindItem(MenuId_Sys_Shutdown)->Enable(false);
m_menuSys.Append(MenuId_Boot_ELF, _("&Run ELF..."),
_("For running raw PS2 binaries directly."));
m_menuSys.AppendSeparator();
m_menuSys.Append(MenuId_Config_FastBoot, _("Fast Boot"),
_("Skips PS2 splash screens when booting from ISO or DVD media"), wxITEM_CHECK);
m_menuSys.Append(MenuId_GameSettingsSubMenu, _("&Game Settings"), &m_GameSettingsSubmenu);
m_GameSettingsSubmenu.Append(MenuId_EnablePatches, _("Automatic &Gamefixes"),
_("Automatically applies needed Gamefixes to known problematic games."), wxITEM_CHECK);
m_GameSettingsSubmenu.Append(MenuId_EnableCheats, _("Enable &Cheats"),
_("Use cheats otherwise known as pnachs from the cheats folder."), wxITEM_CHECK);
m_GameSettingsSubmenu.Append(MenuId_IPC, _("Configure &IPC"), &m_submenuIPC);
m_submenuIPC.Append(MenuId_IPC_Enable, _("&Enable IPC"),
wxEmptyString, wxITEM_CHECK);
m_submenuIPC.Append(MenuId_IPC_Settings, _("IPC &Settings"));
m_GameSettingsSubmenu.Append(MenuId_EnableWideScreenPatches, _("Enable &Widescreen Patches"),
_("Enabling Widescreen Patches may occasionally cause issues."), wxITEM_CHECK);
#ifndef DISABLE_RECORDING
m_GameSettingsSubmenu.Append(MenuId_EnableInputRecording, _("Enable &Input Recording"),
_("Input Recording for controller/keyboard presses, tools for automation and playback."), wxITEM_CHECK);
#endif
m_GameSettingsSubmenu.Append(MenuId_EnableHostFs, _("Enable &Host Filesystem"),
wxEmptyString, wxITEM_CHECK);
m_menuSys.AppendSeparator();
// Implement custom hotkeys (F3) with translatable string intact + not blank in GUI.
wxMenuItem* sysLoadStateItem = m_menuSys.Append(MenuId_Sys_LoadStates, _("&Load state"), &m_LoadStatesSubmenu);
AppendShortcutToMenuOption(*sysLoadStateItem, wxGetApp().GlobalAccels->findKeycodeWithCommandId("States_DefrostCurrentSlot").toTitleizedString());
// Implement custom hotkeys (F1) with translatable string intact + not blank in GUI.
wxMenuItem* sysSaveStateItem = m_menuSys.Append(MenuId_Sys_SaveStates, _("&Save state"), &m_SaveStatesSubmenu);
AppendShortcutToMenuOption(*sysSaveStateItem, wxGetApp().GlobalAccels->findKeycodeWithCommandId("States_FreezeCurrentSlot").toTitleizedString());
m_menuSys.Append(MenuId_EnableBackupStates, _("&Backup before save"), wxEmptyString, wxITEM_CHECK);
m_menuSys.AppendSeparator();
m_menuSys.Append(MenuId_Exit, _("E&xit"),
AddAppName(_("Closing %s may be hazardous to your health.")));
}
void MainEmuFrame::CreateCdvdMenu()
{
// ------------------------------------------------------------------------
wxMenu& isoRecents(wxGetApp().GetRecentIsoMenu());
wxMenu& driveList(wxGetApp().GetDriveListMenu());
m_menuItem_RecentIsoMenu = m_menuCDVD.AppendSubMenu(&isoRecents, _("ISO &Selector"));
m_menuItem_DriveListMenu = m_menuCDVD.AppendSubMenu(&driveList, _("D&rive Selector"));
m_menuCDVD.AppendSeparator();
m_menuCDVD.Append(MenuId_Src_Iso, _("&ISO"), _("Makes the specified ISO image the CDVD source."), wxITEM_RADIO);
m_menuCDVD.Append(MenuId_Src_Disc, _("&Disc"), _("Uses a disc drive as the CDVD source."), wxITEM_RADIO);
m_menuCDVD.Append(MenuId_Src_NoDisc, _("&No disc"), _("Use this to boot into your virtual PS2's BIOS configuration."), wxITEM_RADIO);
#if defined(__FREEBSD__) || defined(__APPLE__)
m_menuItem_DriveListMenu->Enable(false);
m_menuCDVD.Enable(MenuId_Src_Disc, false);
#endif
}
void MainEmuFrame::CreateConfigMenu()
{
m_menuConfig.Append(MenuId_Config_SysSettings, _("General &Settings"));
m_menuConfig.Append(MenuId_Config_McdSettings, _("&Memory Cards"));
m_menuConfig.Append(MenuId_Config_BIOS, _("&BIOS / Folder Selector"));
m_menuConfig.AppendSeparator();
m_menuConfig.Append(MenuId_Config_GS, _("&Graphics Settings"));
m_menuConfig.Append(MenuId_Config_SPU2, _("&Audio Settings"));
m_menuConfig.Append(MenuId_Config_PAD, _("Game&pad Settings"));
m_menuConfig.Append(MenuId_Config_DEV9, _("&Network and HDD Settings"));
m_menuConfig.Append(MenuId_Config_USB, _("&USB Settings"));
m_menuConfig.AppendSeparator();
m_menuConfig.Append(MenuId_Config_Multitap0Toggle, _("Multitap &1"), wxEmptyString, wxITEM_CHECK);
m_menuConfig.Append(MenuId_Config_Multitap1Toggle, _("Multitap &2"), wxEmptyString, wxITEM_CHECK);
m_menuConfig.AppendSeparator();
m_menuConfig.Append(MenuId_ChangeLang, L"Change &Language"); // Always in English
m_menuConfig.Append(MenuId_Config_ResetAll, _("&Clear All Settings"),
AddAppName(_("Clears all %s settings and re-runs the startup wizard.")));
}
void MainEmuFrame::CreateWindowsMenu()
{
m_menuWindow.Append(MenuId_Debug_CreateBlockdump, _("Create &Blockdump"), _("Creates a block dump for debugging purposes."), wxITEM_CHECK);
m_menuWindow.Append(MenuId_Debug_Open, _("&Show Debugger"), wxEmptyString, wxITEM_CHECK);
#if defined(PCSX2_DEVBUILD) || defined(PCSX2_CI)
m_menuWindow.Append(MenuId_GSDump, _("Show &GS Debugger"));
#endif
m_menuWindow.Append(&m_MenuItem_Console);
#if defined(__unix__)
m_menuWindow.AppendSeparator();
m_menuWindow.Append(&m_MenuItem_Console_Stdio);
#endif
}
void MainEmuFrame::CreateCaptureMenu()
{
m_menuCapture.Append(MenuId_Capture_Video, _("Video"), &m_submenuVideoCapture);
// Implement custom hotkeys (F12) with translatable string intact + not blank in GUI.
wxMenuItem* sysVideoCaptureItem = m_submenuVideoCapture.Append(MenuId_Capture_Video_Record, _("Start Video Capture"));
AppendShortcutToMenuOption(*sysVideoCaptureItem, wxGetApp().GlobalAccels->findKeycodeWithCommandId("Sys_RecordingToggle").toTitleizedString());
sysVideoCaptureItem = m_submenuVideoCapture.Append(MenuId_Capture_Video_Stop, _("Stop Video Capture"));
sysVideoCaptureItem->Enable(false);
AppendShortcutToMenuOption(*sysVideoCaptureItem, wxGetApp().GlobalAccels->findKeycodeWithCommandId("Sys_RecordingToggle").toTitleizedString());
m_submenuVideoCapture.AppendSeparator();
m_submenuVideoCapture.Append(MenuId_Capture_Video_IncludeAudio, _("Include Audio"),
_("Enables/disables the creation of a synchronized wav audio file when capturing video footage."), wxITEM_CHECK);
// Implement custom hotkeys (F8) + (Shift + F8) + (Ctrl + Shift + F8) with translatable string intact + not blank in GUI.
// Fixme: GlobalCommands.cpp L1029-L1031 is having issues because FrameForGS already maps the hotkey first.
// Fixme: When you uncomment L1029-L1031 on that file; Linux says that Ctrl is already used for something else and will append (Shift + F8) while Windows will (Ctrl + Shift + F8)
m_menuCapture.Append(MenuId_Capture_Screenshot, _("Screenshot"), &m_submenuScreenshot);
wxMenuItem* sysScreenShotItem = m_submenuScreenshot.Append(MenuId_Capture_Screenshot_Screenshot, _("Take Screenshot"));
// HACK: in AcceleratorDictionary::Map the Sys_TakeSnapshot entry gets Shift and Cmd (Ctrl) hardcoded to it because it is similarly hardcoded in GS
// So... remove such modifiers as the GUI menu entry is only for the base keybinding without modifiers.
// We can be confident in doing so, as if a user adds these modifiers themselves, the same function rejects it.
KeyAcceleratorCode keyCode = wxGetApp().GlobalAccels->findKeycodeWithCommandId("Sys_TakeSnapshot");
keyCode.Shift(false);
keyCode.Cmd(false);
AppendShortcutToMenuOption(*sysScreenShotItem, keyCode.toTitleizedString());
m_submenuScreenshot.Append(MenuId_Capture_Screenshot_Screenshot_As, _("Save Screenshot As..."));
}
void MainEmuFrame::CreateInputRecordingMenu()
{
#ifndef DISABLE_RECORDING
m_menuRecording.Append(MenuId_Recording_New, _("New"), _("Create a new input recording."))->Enable(false);
m_menuRecording.Append(MenuId_Recording_Stop, _("Stop"), _("Stop the active input recording."))->Enable(false);
m_menuRecording.Append(MenuId_Recording_Play, _("Play"), _("Playback an existing input recording."))->Enable(false);
m_menuRecording.AppendSeparator();
m_menuRecording.Append(MenuId_Recording_Settings, _("Settings"), &m_submenu_recording_settings);
wxString frame_advance_label = wxString(_("Configure Frame Advance"));
frame_advance_label.Append(fmt::format(" ({})", g_Conf->inputRecording.m_frame_advance_amount));
m_submenu_recording_settings.Append(MenuId_Recording_Config_FrameAdvance, frame_advance_label, _("Change the amount of frames advanced each time"));
m_menuRecording.AppendSeparator();
m_menuRecording.Append(MenuId_Recording_TogglePause, _("Toggle Pause"), _("Pause or resume emulation on the fly."))->Enable(false);
m_menuRecording.Append(MenuId_Recording_FrameAdvance, _("Frame Advance"), _("Advance emulation forward by a single frame at a time."))->Enable(false);
m_menuRecording.Append(MenuId_Recording_ToggleRecordingMode, _("Toggle Recording Mode"), _("Save/playback inputs to/from the recording file."))->Enable(false);
m_menuRecording.AppendSeparator();
m_menuRecording.Append(MenuId_Recording_VirtualPad_Port0, _("Virtual Pad (Port 1)"));
m_menuRecording.Append(MenuId_Recording_VirtualPad_Port1, _("Virtual Pad (Port 2)"));
#endif
}
void MainEmuFrame::CreateHelpMenu()
{
m_menuHelp.Append(MenuId_Help_GetStarted, _("&Getting Started"));
m_menuHelp.Append(MenuId_Help_Compatibility, _("&Compatibility"));
m_menuHelp.AppendSeparator();
m_menuHelp.Append(MenuId_Help_Website, _("&Website"));
m_menuHelp.Append(MenuId_Help_Wiki, _("&Wiki"));
m_menuHelp.Append(MenuId_Help_Forums, _("&Support Forums"));
m_menuHelp.Append(MenuId_Help_Github, _("&GitHub Repository"));
m_menuHelp.AppendSeparator();
m_menuHelp.Append(MenuId_About, _("&About"));
}
// ------------------------------------------------------------------------
MainEmuFrame::MainEmuFrame(wxWindow* parent, const wxString& title)
: wxFrame(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE & ~(wxMAXIMIZE_BOX | wxRESIZE_BORDER))
, m_statusbar(*CreateStatusBar(2, 0))
, m_background(new wxStaticBitmap(this, wxID_ANY, wxGetApp().GetLogoBitmap()))
// All menu components must be created on the heap!
, m_menubar(*new wxMenuBar())
, m_menuCDVD(*new wxMenu())
, m_menuSys(*new wxMenu())
, m_menuConfig(*new wxMenu())
, m_menuWindow(*new wxMenu())
, m_menuCapture(*new wxMenu())
, m_submenuVideoCapture(*new wxMenu())
, m_submenuIPC(*new wxMenu())
, m_submenuScreenshot(*new wxMenu())
#ifndef DISABLE_RECORDING
, m_menuRecording(*new wxMenu())
, m_submenu_recording_settings(*new wxMenu())
#endif
, m_menuHelp(*new wxMenu())
, m_LoadStatesSubmenu(*MakeStatesSubMenu(MenuId_State_Load01, MenuId_State_LoadBackup))
, m_SaveStatesSubmenu(*MakeStatesSubMenu(MenuId_State_Save01))
, m_GameSettingsSubmenu(*new wxMenu())
, m_MenuItem_Console(*new wxMenuItem(&m_menuWindow, MenuId_Console, _("&Show Program Log"), wxEmptyString, wxITEM_CHECK))
#if defined(__unix__)
, m_MenuItem_Console_Stdio(*new wxMenuItem(&m_menuWindow, MenuId_Console_Stdio, _("&Program Log to Stdio"), wxEmptyString, wxITEM_CHECK))
#endif
{
m_RestartEmuOnDelete = false;
m_capturingVideo = false;
// ------------------------------------------------------------------------
// Initial menubar setup. This needs to be done first so that the menu bar's visible size
// can be factored into the window size (which ends up being background+status+menus)
m_menubar.Append(&m_menuSys, _("&System"));
m_menubar.Append(&m_menuCDVD, _("CD&VD"));
m_menubar.Append(&m_menuConfig, _("&Config"));
m_menubar.Append(&m_menuWindow, _("&Debug"));
m_menubar.Append(&m_menuCapture, _("Captu&re"));
SetMenuBar(&m_menubar);
#ifndef DISABLE_RECORDING
// Append the Recording options if previously enabled and setting has been picked up from ini
if (g_Conf->EmuOptions.EnableRecordingTools)
{
m_menubar.Append(&m_menuRecording, _("&Input Record"));
}
#endif
m_menubar.Append(&m_menuHelp, _("&Help"));
// ------------------------------------------------------------------------
// The background logo and its window size are different on Windows. Use the
// background logo size, which is what it'll eventually be resized to.
wxSize backsize(m_background->GetBitmap().GetWidth(), m_background->GetBitmap().GetHeight());
wxString wintitle;
if (PCSX2_isReleaseVersion)
{
// stable releases, with a simple title.
wintitle.Printf(L"%s %d.%d.%d", pxGetAppName().c_str(), PCSX2_VersionHi, PCSX2_VersionMid, PCSX2_VersionLo);
}
else
{
// beta / development editions, which feature revision number and compile date.
if (strlen(GIT_REV) > 5)
{
wintitle.Printf(L"%s %s", pxGetAppName().c_str(), GIT_REV);
}
else
{
wintitle.Printf(L"%s %d.%d.%d-%lld%s (git)", pxGetAppName().c_str(), PCSX2_VersionHi, PCSX2_VersionMid,
PCSX2_VersionLo, SVN_REV, SVN_MODS ? L"m" : wxEmptyString);
}
}
SetTitle(wintitle);
// Ideally the __WXMSW__ port should use the embedded IDI_ICON2 icon, because wxWidgets sucks and
// loses the transparency information when loading bitmaps into icons. But for some reason
// I cannot get it to work despite following various examples to the letter.
SetIcons(wxGetApp().GetIconBundle());
int m_statusbar_widths[] = {(int)-20, (int)-3, (int)-2};
m_statusbar.SetFieldsCount(3);
m_statusbar.SetStatusWidths(3, m_statusbar_widths);
m_statusbar.SetStatusText(wxEmptyString, 0);
wxBoxSizer& joe(*new wxBoxSizer(wxVERTICAL));
joe.Add(m_background);
SetSizerAndFit(&joe);
// Makes no sense, but this is needed for the window size to be correct for
// 200% DPI on Windows. The SetSizerAndFit is supposed to be doing the exact
// same thing.
GetSizer()->SetSizeHints(this);
// Use default window position if the configured windowpos is invalid (partially offscreen)
if (g_Conf->MainGuiPosition == wxDefaultPosition || !pxIsValidWindowPosition(*this, g_Conf->MainGuiPosition))
g_Conf->MainGuiPosition = GetScreenPosition();
else
SetPosition(g_Conf->MainGuiPosition);
// Updating console log positions after the main window has been fitted to its sizer ensures
// proper docked positioning, since the main window's size is invalid until after the sizer
// has been set/fit.
InitLogBoxPosition(g_Conf->ProgLogBox);
CreatePcsx2Menu();
CreateCdvdMenu();
CreateConfigMenu();
CreateWindowsMenu();
CreateCaptureMenu();
#ifndef DISABLE_RECORDING
CreateInputRecordingMenu();
#endif
CreateHelpMenu();
m_MenuItem_Console.Check(g_Conf->ProgLogBox.Visible);
ConnectMenus();
Bind(wxEVT_MOVE, &MainEmuFrame::OnMoveAround, this);
Bind(wxEVT_CLOSE_WINDOW, &MainEmuFrame::OnCloseWindow, this);
Bind(wxEVT_SET_FOCUS, &MainEmuFrame::OnFocus, this);
Bind(wxEVT_ACTIVATE, &MainEmuFrame::OnActivate, this);
PushEventHandler(&wxGetApp().GetRecentIsoManager());
SetDropTarget(new IsoDropTarget(this));
ApplyCoreStatus();
ApplySettings();
}
MainEmuFrame::~MainEmuFrame()
{
try
{
if (m_RestartEmuOnDelete)
{
sApp.SetExitOnFrameDelete(false);
sApp.PostAppMethod(&Pcsx2App::DetectCpuAndUserMode);
sApp.WipeUserModeSettings();
}
}
DESTRUCTOR_CATCHALL
}
void MainEmuFrame::DoGiveHelp(const wxString& text, bool show)
{
_parent::DoGiveHelp(text, show);
wxGetApp().GetProgramLog()->DoGiveHelp(text, show);
}
// ----------------------------------------------------------------------------
// OnFocus / OnActivate : Special implementation to "connect" the console log window
// with the main frame window. When one is clicked, the other is assured to be brought
// to the foreground with it. (Currently only MSW only, as wxWidgets appears to have no
// equivalent to this). Both OnFocus and OnActivate are handled because Focus events do
// not propagate up the window hierarchy, and on Activate events don't always get sent
// on the first focusing event after PCSX2 starts.
void MainEmuFrame::OnFocus(wxFocusEvent& evt)
{
if (ConsoleLogFrame* logframe = wxGetApp().GetProgramLog())
MSW_SetWindowAfter(logframe->GetHandle(), GetHandle());
evt.Skip();
}
void MainEmuFrame::OnActivate(wxActivateEvent& evt)
{
if (ConsoleLogFrame* logframe = wxGetApp().GetProgramLog())
MSW_SetWindowAfter(logframe->GetHandle(), GetHandle());
evt.Skip();
}
// ----------------------------------------------------------------------------
void MainEmuFrame::ApplyCoreStatus()
{
ApplySuspendStatus();
ApplyCDVDStatus();
}
void MainEmuFrame::ApplySuspendStatus()
{
// [TODO] : Ideally each of these items would bind a listener instance to the AppCoreThread
// dispatcher, and modify their states accordingly. This is just a hack (for now) -- air
if (wxMenuItem* susres = GetMenuBar()->FindItem(MenuId_Sys_SuspendResume))
{
if (!CoreThread.IsClosing())
{
susres->Enable();
susres->SetItemLabel(_("Paus&e"));
susres->SetHelp(_("Safely pauses emulation and preserves the PS2 state."));
}
else
{
bool ActiveVM = SysHasValidState();
susres->Enable(ActiveVM);
if (ActiveVM)
{
susres->SetItemLabel(_("R&esume"));
susres->SetHelp(_("Resumes the suspended emulation state."));
}
else
{
susres->SetItemLabel(_("Pause/Resume"));
susres->SetHelp(_("No emulation state is active; cannot suspend or resume."));
}
}
// Re-init keybinding after changing the label.
AppendShortcutToMenuOption(*susres, wxGetApp().GlobalAccels->findKeycodeWithCommandId("Sys_SuspendResume").toTitleizedString());
}
}
void MainEmuFrame::ApplyCDVDStatus()
{
const CDVD_SourceType Source = g_Conf->CdvdSource;
wxMenuItem* cdvd_menu = GetMenuBar()->FindItem(MenuId_Boot_CDVD);
wxString label;
wxString help_text = _("Use fast boot to skip PS2 startup and splash screens.");
switch (Source)
{
case CDVD_SourceType::Iso:
label = _("Boot ISO");
break;
case CDVD_SourceType::Disc:
label = _("Boot CDVD");
break;
case CDVD_SourceType::NoDisc:
label = _("Boot BIOS");
break;
default:
label = _("Boot BIOS");
break;
}
cdvd_menu->SetItemLabel(label);
cdvd_menu->SetHelp(help_text);
}
//Apply a config to the menu such that the menu reflects it properly
void MainEmuFrame::ApplySettings()
{
ApplyConfigToGui(*g_Conf);
}
//MainEmuFrame needs to be aware which items are affected by presets if AppConfig::APPLY_FLAG_FROM_PRESET is on.
//currently only EnablePatches is affected when the settings come from a preset.
void MainEmuFrame::ApplyConfigToGui(AppConfig& configToApply, int flags)
{
wxMenuBar& menubar(*GetMenuBar());
menubar.Check(MenuId_EnablePatches, configToApply.EmuOptions.EnablePatches);
menubar.Enable(MenuId_EnablePatches, !configToApply.EnablePresets);
if (!(flags & AppConfig::APPLY_FLAG_FROM_PRESET))
{ //these should not be affected by presets
menubar.Check(MenuId_EnableBackupStates, configToApply.EmuOptions.BackupSavestate);
menubar.Check(MenuId_EnableCheats, configToApply.EmuOptions.EnableCheats);
menubar.Check(MenuId_IPC_Enable, configToApply.EmuOptions.EnableIPC);
menubar.Check(MenuId_EnableWideScreenPatches, configToApply.EmuOptions.EnableWideScreenPatches);
menubar.Check(MenuId_Capture_Video_IncludeAudio, configToApply.AudioCapture.EnableAudio);
#ifndef DISABLE_RECORDING
menubar.Check(MenuId_EnableInputRecording, configToApply.EmuOptions.EnableRecordingTools);
wxString frame_advance_label = wxString(_("Configure Frame Advance"));
frame_advance_label.Append(fmt::format(" ({})", configToApply.inputRecording.m_frame_advance_amount));
m_submenu_recording_settings.SetLabel(MenuId_Recording_Config_FrameAdvance, frame_advance_label);
g_InputRecordingControls.setFrameAdvanceAmount(configToApply.inputRecording.m_frame_advance_amount);
#endif
menubar.Check(MenuId_EnableHostFs, configToApply.EmuOptions.HostFs);
menubar.Check(MenuId_Debug_CreateBlockdump, configToApply.EmuOptions.CdvdDumpBlocks);
#if defined(__unix__)
menubar.Check(MenuId_Console_Stdio, configToApply.EmuOptions.ConsoleToStdio);
#endif
menubar.Check(MenuId_Config_Multitap0Toggle, configToApply.EmuOptions.MultitapPort0_Enabled);
menubar.Check(MenuId_Config_Multitap1Toggle, configToApply.EmuOptions.MultitapPort1_Enabled);
menubar.Check(MenuId_Config_FastBoot, configToApply.EnableFastBoot);
}
UpdateCdvdSrcSelection(); //shouldn't be affected by presets but updates from g_Conf anyway and not from configToApply, so no problem here.
}
//write pending preset settings from the gui to g_Conf,
// without triggering an overall "settingsApplied" event.
void MainEmuFrame::CommitPreset_noTrigger()
{
wxMenuBar& menubar(*GetMenuBar());
g_Conf->EmuOptions.EnablePatches = menubar.IsChecked(MenuId_EnablePatches);
}
void MainEmuFrame::AppendShortcutToMenuOption(wxMenuItem& item, wxString keyCodeStr)
{
wxString text = item.GetItemLabel();
const size_t tabPos = text.rfind(L'\t');
item.SetItemLabel(text.Mid(0, tabPos) + L"\t" + keyCodeStr);
}
#ifndef DISABLE_RECORDING
void MainEmuFrame::initializeRecordingMenuItem(MenuIdentifiers menuId, wxString keyCodeStr, bool enable)
{
wxMenuItem& item = *m_menuRecording.FindChildItem(menuId);
wxString text = item.GetItemLabel();
const size_t tabPos = text.rfind(L'\t');
item.SetItemLabel(text.Mid(0, tabPos) + L"\t" + keyCodeStr);
item.Enable(enable);
}
void MainEmuFrame::enableRecordingMenuItem(MenuIdentifiers menuId, bool enable)
{
wxMenuItem& item = *m_menuRecording.FindChildItem(menuId);
item.Enable(enable);
}
#endif