dolphin/Source/Core/DolphinQt2/MainWindow.cpp

476 lines
14 KiB
C++
Raw Normal View History

2015-11-27 08:33:07 +00:00
// Copyright 2015 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <QDir>
#include <QFileDialog>
#include <QIcon>
#include <QMessageBox>
#include "Common/Common.h"
Boot: Clean up the boot code * Move out boot parameters to a separate struct, which is not part of SConfig/ConfigManager because there is no reason for it to be there. * Move out file name parsing and constructing the appropriate params from paths to a separate function that does that, and only that. * For every different boot type we support, add a proper struct with only the required parameters, with descriptive names and use std::variant to only store what we need. * Clean up the bHLE_BS2 stuff which made no sense sometimes. Now instead of using bHLE_BS2 for two different things, both for storing the user config setting and as a runtime boot parameter, we simply replace the Disc boot params with BootParameters::IPL. * Const correctness so it's clear what can or cannot update the config. * Drop unused parameters and unneeded checks. * Make a few checks a lot more concise. (Looking at you, extension checks for disc images.) * Remove a mildly terrible workaround where we needed to pass an empty string in order to boot the GC IPL without any game inserted. (Not required anymore thanks to std::variant and std::optional.) The motivation for this are multiple: cleaning up and being able to add support for booting an installed NAND title. Without this change, it'd be pretty much impossible to implement that. Also, using std::visit with std::variant makes the compiler do additional type checks: now we're guaranteed that the boot code will handle all boot types and no invalid boot type will be possible.
2017-05-27 13:43:40 +00:00
#include "Core/Boot/Boot.h"
2015-11-27 08:33:07 +00:00
#include "Core/BootManager.h"
#include "Core/ConfigManager.h"
2015-11-27 08:33:07 +00:00
#include "Core/Core.h"
2017-05-23 20:12:01 +00:00
#include "Core/HW/GCKeyboard.h"
#include "Core/HW/GCPad.h"
#include "Core/HW/ProcessorInterface.h"
2017-05-23 20:12:01 +00:00
#include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "Core/HotkeyManager.h"
#include "Core/Movie.h"
2017-05-09 16:49:10 +00:00
#include "Core/NetPlayProto.h"
#include "Core/State.h"
#include "DolphinQt2/AboutDialog.h"
2017-05-09 16:49:10 +00:00
#include "DolphinQt2/Config/ControllersWindow.h"
#include "DolphinQt2/Config/Mapping/MappingWindow.h"
#include "DolphinQt2/Config/SettingsWindow.h"
2015-11-27 08:33:07 +00:00
#include "DolphinQt2/Host.h"
#include "DolphinQt2/HotkeyScheduler.h"
2015-11-27 08:33:07 +00:00
#include "DolphinQt2/MainWindow.h"
#include "DolphinQt2/QtUtils/FocusEventFilter.h"
2015-11-27 08:33:07 +00:00
#include "DolphinQt2/Resources.h"
#include "DolphinQt2/Settings.h"
2015-11-27 08:33:07 +00:00
2017-05-23 20:12:01 +00:00
#include "InputCommon/ControllerInterface/ControllerInterface.h"
2015-11-27 08:33:07 +00:00
MainWindow::MainWindow() : QMainWindow(nullptr)
{
setWindowTitle(QString::fromStdString(scm_rev_str));
setWindowIcon(QIcon(Resources::GetMisc(Resources::LOGO_SMALL)));
setUnifiedTitleAndToolBarOnMac(true);
2015-11-27 08:33:07 +00:00
CreateComponents();
2016-02-10 04:42:06 +00:00
ConnectGameList();
ConnectToolBar();
ConnectRenderWidget();
ConnectStack();
ConnectMenuBar();
2017-05-23 20:12:01 +00:00
InitControllers();
2015-11-27 08:33:07 +00:00
}
2016-02-15 01:04:16 +00:00
MainWindow::~MainWindow()
{
m_render_widget->deleteLater();
ShutdownControllers();
2016-02-15 01:04:16 +00:00
}
2017-05-23 20:12:01 +00:00
void MainWindow::InitControllers()
{
if (g_controller_interface.IsInit())
return;
g_controller_interface.Initialize(reinterpret_cast<void*>(winId()));
Pad::Initialize();
Keyboard::Initialize();
Wiimote::Initialize(Wiimote::InitializeMode::DO_NOT_WAIT_FOR_WIIMOTES);
m_hotkey_scheduler = new HotkeyScheduler();
m_hotkey_scheduler->Start();
ConnectHotkeys();
2017-05-23 20:12:01 +00:00
}
void MainWindow::ShutdownControllers()
{
m_hotkey_scheduler->Stop();
g_controller_interface.Shutdown();
Pad::Shutdown();
Keyboard::Shutdown();
Wiimote::Shutdown();
HotkeyManagerEmu::Shutdown();
m_hotkey_scheduler->deleteLater();
}
static void InstallHotkeyFilter(QDialog* dialog)
{
auto* filter = new FocusEventFilter();
dialog->installEventFilter(filter);
filter->connect(filter, &FocusEventFilter::focusOutEvent, [] { HotkeyManagerEmu::Enable(true); });
filter->connect(filter, &FocusEventFilter::focusInEvent, [] { HotkeyManagerEmu::Enable(false); });
}
2016-02-10 04:42:06 +00:00
void MainWindow::CreateComponents()
2015-11-27 08:33:07 +00:00
{
m_menu_bar = new MenuBar(this);
m_tool_bar = new ToolBar(this);
m_game_list = new GameList(this);
m_render_widget = new RenderWidget;
m_stack = new QStackedWidget(this);
2017-05-09 16:49:10 +00:00
m_controllers_window = new ControllersWindow(this);
m_settings_window = new SettingsWindow(this);
m_hotkey_window = new MappingWindow(this, 0);
InstallHotkeyFilter(m_hotkey_window);
InstallHotkeyFilter(m_controllers_window);
InstallHotkeyFilter(m_settings_window);
2015-11-27 08:33:07 +00:00
}
2016-02-10 04:42:06 +00:00
void MainWindow::ConnectMenuBar()
2015-11-27 08:33:07 +00:00
{
setMenuBar(m_menu_bar);
// File
connect(m_menu_bar, &MenuBar::Open, this, &MainWindow::Open);
connect(m_menu_bar, &MenuBar::Exit, this, &MainWindow::close);
// Emulation
connect(m_menu_bar, &MenuBar::Pause, this, &MainWindow::Pause);
connect(m_menu_bar, &MenuBar::Play, this, &MainWindow::Play);
connect(m_menu_bar, &MenuBar::Stop, this, &MainWindow::Stop);
connect(m_menu_bar, &MenuBar::Reset, this, &MainWindow::Reset);
connect(m_menu_bar, &MenuBar::Fullscreen, this, &MainWindow::FullScreen);
connect(m_menu_bar, &MenuBar::FrameAdvance, this, &MainWindow::FrameAdvance);
connect(m_menu_bar, &MenuBar::Screenshot, this, &MainWindow::ScreenShot);
connect(m_menu_bar, &MenuBar::StateLoad, this, &MainWindow::StateLoad);
connect(m_menu_bar, &MenuBar::StateSave, this, &MainWindow::StateSave);
connect(m_menu_bar, &MenuBar::StateLoadSlot, this, &MainWindow::StateLoadSlot);
connect(m_menu_bar, &MenuBar::StateSaveSlot, this, &MainWindow::StateSaveSlot);
connect(m_menu_bar, &MenuBar::StateLoadSlotAt, this, &MainWindow::StateLoadSlotAt);
connect(m_menu_bar, &MenuBar::StateSaveSlotAt, this, &MainWindow::StateSaveSlotAt);
connect(m_menu_bar, &MenuBar::StateLoadUndo, this, &MainWindow::StateLoadUndo);
connect(m_menu_bar, &MenuBar::StateSaveUndo, this, &MainWindow::StateSaveUndo);
connect(m_menu_bar, &MenuBar::StateSaveOldest, this, &MainWindow::StateSaveOldest);
connect(m_menu_bar, &MenuBar::SetStateSlot, this, &MainWindow::SetStateSlot);
// Options
connect(m_menu_bar, &MenuBar::ConfigureHotkeys, this, &MainWindow::ShowHotkeyDialog);
// View
connect(m_menu_bar, &MenuBar::ShowTable, m_game_list, &GameList::SetTableView);
connect(m_menu_bar, &MenuBar::ShowList, m_game_list, &GameList::SetListView);
2017-05-08 17:03:59 +00:00
connect(m_menu_bar, &MenuBar::ColumnVisibilityToggled, m_game_list,
&GameList::OnColumnVisibilityToggled);
connect(m_menu_bar, &MenuBar::ShowAboutDialog, this, &MainWindow::ShowAboutDialog);
connect(this, &MainWindow::EmulationStarted, m_menu_bar, &MenuBar::EmulationStarted);
connect(this, &MainWindow::EmulationPaused, m_menu_bar, &MenuBar::EmulationPaused);
connect(this, &MainWindow::EmulationStopped, m_menu_bar, &MenuBar::EmulationStopped);
2017-05-09 16:49:10 +00:00
connect(this, &MainWindow::EmulationStarted, this,
[=]() { m_controllers_window->OnEmulationStateChanged(true); });
connect(this, &MainWindow::EmulationStopped, this,
[=]() { m_controllers_window->OnEmulationStateChanged(false); });
2015-11-27 08:33:07 +00:00
}
void MainWindow::ConnectHotkeys()
{
connect(m_hotkey_scheduler, &HotkeyScheduler::ExitHotkey, this, &MainWindow::close);
connect(m_hotkey_scheduler, &HotkeyScheduler::PauseHotkey, this, &MainWindow::Pause);
connect(m_hotkey_scheduler, &HotkeyScheduler::StopHotkey, this, &MainWindow::Stop);
connect(m_hotkey_scheduler, &HotkeyScheduler::ScreenShotHotkey, this, &MainWindow::ScreenShot);
connect(m_hotkey_scheduler, &HotkeyScheduler::FullScreenHotkey, this, &MainWindow::FullScreen);
connect(m_hotkey_scheduler, &HotkeyScheduler::StateLoadSlotHotkey, this,
&MainWindow::StateLoadSlot);
connect(m_hotkey_scheduler, &HotkeyScheduler::StateSaveSlotHotkey, this,
&MainWindow::StateSaveSlot);
connect(m_hotkey_scheduler, &HotkeyScheduler::SetStateSlotHotkey, this,
&MainWindow::SetStateSlot);
}
2016-02-10 04:42:06 +00:00
void MainWindow::ConnectToolBar()
2015-11-27 08:33:07 +00:00
{
addToolBar(m_tool_bar);
connect(m_tool_bar, &ToolBar::OpenPressed, this, &MainWindow::Open);
connect(m_tool_bar, &ToolBar::PlayPressed, this, &MainWindow::Play);
connect(m_tool_bar, &ToolBar::PausePressed, this, &MainWindow::Pause);
connect(m_tool_bar, &ToolBar::StopPressed, this, &MainWindow::Stop);
connect(m_tool_bar, &ToolBar::FullScreenPressed, this, &MainWindow::FullScreen);
connect(m_tool_bar, &ToolBar::ScreenShotPressed, this, &MainWindow::ScreenShot);
connect(m_tool_bar, &ToolBar::SettingsPressed, this, &MainWindow::ShowSettingsWindow);
2017-05-09 16:49:10 +00:00
connect(m_tool_bar, &ToolBar::ControllersPressed, this, &MainWindow::ShowControllersWindow);
connect(this, &MainWindow::EmulationStarted, m_tool_bar, &ToolBar::EmulationStarted);
connect(this, &MainWindow::EmulationPaused, m_tool_bar, &ToolBar::EmulationPaused);
connect(this, &MainWindow::EmulationStopped, m_tool_bar, &ToolBar::EmulationStopped);
2015-11-27 08:33:07 +00:00
}
2016-02-10 04:42:06 +00:00
void MainWindow::ConnectGameList()
2015-11-27 08:33:07 +00:00
{
connect(m_game_list, &GameList::GameSelected, this, &MainWindow::Play);
2015-11-27 08:33:07 +00:00
}
2016-02-10 04:42:06 +00:00
void MainWindow::ConnectRenderWidget()
2015-11-27 08:33:07 +00:00
{
m_rendering_to_main = false;
m_render_widget->hide();
connect(m_render_widget, &RenderWidget::EscapePressed, this, &MainWindow::Stop);
connect(m_render_widget, &RenderWidget::Closed, this, &MainWindow::ForceStop);
2015-11-27 08:33:07 +00:00
}
2016-02-10 04:42:06 +00:00
void MainWindow::ConnectStack()
2015-11-27 08:33:07 +00:00
{
m_stack->setMinimumSize(800, 600);
m_stack->addWidget(m_game_list);
setCentralWidget(m_stack);
2015-11-27 08:33:07 +00:00
}
void MainWindow::Open()
{
QString file = QFileDialog::getOpenFileName(
this, tr("Select a File"), QDir::currentPath(),
2016-12-17 13:48:49 +00:00
tr("All GC/Wii files (*.elf *.dol *.gcm *.iso *.tgc *.wbfs *.ciso *.gcz *.wad);;"
"All Files (*)"));
if (!file.isEmpty())
StartGame(file);
2015-11-27 08:33:07 +00:00
}
void MainWindow::Play()
{
// If we're in a paused game, start it up again.
// Otherwise, play the selected game, if there is one.
// Otherwise, play the default game.
// Otherwise, play the last played game, if there is one.
// Otherwise, prompt for a new game.
if (Core::GetState() == Core::State::Paused)
{
Core::SetState(Core::State::Running);
emit EmulationStarted();
}
else
{
QString selection = m_game_list->GetSelectedGame();
if (selection.length() > 0)
{
StartGame(selection);
}
else
{
QString default_path = Settings::Instance().GetDefaultGame();
if (!default_path.isEmpty() && QFile::exists(default_path))
{
StartGame(default_path);
}
else
{
2017-06-04 20:43:41 +00:00
Open();
}
}
}
2015-11-27 08:33:07 +00:00
}
void MainWindow::Pause()
{
Core::SetState(Core::State::Paused);
emit EmulationPaused();
2015-11-27 08:33:07 +00:00
}
bool MainWindow::Stop()
{
bool stop = true;
if (Settings::Instance().GetConfirmStop())
{
// We could pause the game here and resume it if they say no.
QMessageBox::StandardButton confirm;
confirm = QMessageBox::question(m_render_widget, tr("Confirm"), tr("Stop emulation?"));
stop = (confirm == QMessageBox::Yes);
}
2015-11-27 08:33:07 +00:00
if (stop)
{
ForceStop();
2015-11-27 08:33:07 +00:00
#ifdef Q_OS_WIN
// Allow windows to idle or turn off display again
SetThreadExecutionState(ES_CONTINUOUS);
#endif
}
return stop;
2015-11-27 08:33:07 +00:00
}
void MainWindow::ForceStop()
{
BootManager::Stop();
HideRenderWidget();
emit EmulationStopped();
2015-11-27 08:33:07 +00:00
}
void MainWindow::Reset()
{
if (Movie::IsRecordingInput())
2016-08-04 16:54:45 +00:00
Movie::SetReset(true);
ProcessorInterface::ResetButton_Tap();
}
void MainWindow::FrameAdvance()
{
Movie::DoFrameStep();
EmulationPaused();
}
2015-11-27 08:33:07 +00:00
void MainWindow::FullScreen()
{
// If the render widget is fullscreen we want to reset it to whatever is in
// settings. If it's set to be fullscreen then it just remakes the window,
// which probably isn't ideal.
bool was_fullscreen = m_render_widget->isFullScreen();
HideRenderWidget();
if (was_fullscreen)
ShowRenderWidget();
else
m_render_widget->showFullScreen();
2015-11-27 08:33:07 +00:00
}
void MainWindow::ScreenShot()
{
Core::SaveScreenShot();
2015-11-27 08:33:07 +00:00
}
void MainWindow::StartGame(const QString& path)
2015-11-27 08:33:07 +00:00
{
// If we're running, only start a new game once we've stopped the last.
if (Core::GetState() != Core::State::Uninitialized)
{
if (!Stop())
return;
}
// Boot up, show an error if it fails to load the game.
Boot: Clean up the boot code * Move out boot parameters to a separate struct, which is not part of SConfig/ConfigManager because there is no reason for it to be there. * Move out file name parsing and constructing the appropriate params from paths to a separate function that does that, and only that. * For every different boot type we support, add a proper struct with only the required parameters, with descriptive names and use std::variant to only store what we need. * Clean up the bHLE_BS2 stuff which made no sense sometimes. Now instead of using bHLE_BS2 for two different things, both for storing the user config setting and as a runtime boot parameter, we simply replace the Disc boot params with BootParameters::IPL. * Const correctness so it's clear what can or cannot update the config. * Drop unused parameters and unneeded checks. * Make a few checks a lot more concise. (Looking at you, extension checks for disc images.) * Remove a mildly terrible workaround where we needed to pass an empty string in order to boot the GC IPL without any game inserted. (Not required anymore thanks to std::variant and std::optional.) The motivation for this are multiple: cleaning up and being able to add support for booting an installed NAND title. Without this change, it'd be pretty much impossible to implement that. Also, using std::visit with std::variant makes the compiler do additional type checks: now we're guaranteed that the boot code will handle all boot types and no invalid boot type will be possible.
2017-05-27 13:43:40 +00:00
if (!BootManager::BootCore(BootParameters::GenerateFromFile(path.toStdString())))
{
QMessageBox::critical(this, tr("Error"), tr("Failed to init core"), QMessageBox::Ok);
return;
}
ShowRenderWidget();
emit EmulationStarted();
#ifdef Q_OS_WIN
// Prevents Windows from sleeping, turning off the display, or idling
EXECUTION_STATE shouldScreenSave =
SConfig::GetInstance().bDisableScreenSaver ? ES_DISPLAY_REQUIRED : 0;
SetThreadExecutionState(ES_CONTINUOUS | shouldScreenSave | ES_SYSTEM_REQUIRED);
#endif
2015-11-27 08:33:07 +00:00
}
void MainWindow::ShowRenderWidget()
{
auto& settings = Settings::Instance();
if (settings.GetRenderToMain())
{
// If we're rendering to main, add it to the stack and update our title when necessary.
m_rendering_to_main = true;
m_stack->setCurrentIndex(m_stack->addWidget(m_render_widget));
connect(Host::GetInstance(), &Host::RequestTitle, this, &MainWindow::setWindowTitle);
}
else
{
// Otherwise, just show it.
m_rendering_to_main = false;
if (settings.GetFullScreen())
{
m_render_widget->showFullScreen();
}
else
{
m_render_widget->setFixedSize(settings.GetRenderWindowSize());
m_render_widget->showNormal();
}
}
2015-11-27 08:33:07 +00:00
}
void MainWindow::HideRenderWidget()
{
if (m_rendering_to_main)
{
// Remove the widget from the stack and reparent it to nullptr, so that it can draw
// itself in a new window if it wants. Disconnect the title updates.
m_stack->removeWidget(m_render_widget);
m_render_widget->setParent(nullptr);
m_rendering_to_main = false;
disconnect(Host::GetInstance(), &Host::RequestTitle, this, &MainWindow::setWindowTitle);
setWindowTitle(QString::fromStdString(scm_rev_str));
}
m_render_widget->hide();
2015-11-27 08:33:07 +00:00
}
2016-02-10 04:42:06 +00:00
2017-05-09 16:49:10 +00:00
void MainWindow::ShowControllersWindow()
{
m_controllers_window->show();
m_controllers_window->raise();
m_controllers_window->activateWindow();
}
void MainWindow::ShowSettingsWindow()
{
m_settings_window->show();
m_settings_window->raise();
m_settings_window->activateWindow();
}
void MainWindow::ShowAboutDialog()
{
AboutDialog* about = new AboutDialog(this);
about->show();
}
void MainWindow::ShowHotkeyDialog()
{
m_hotkey_window->ChangeMappingType(MappingWindow::Type::MAPPING_HOTKEYS);
m_hotkey_window->show();
m_hotkey_window->raise();
m_hotkey_window->activateWindow();
}
void MainWindow::StateLoad()
{
QString path = QFileDialog::getOpenFileName(this, tr("Select a File"), QDir::currentPath(),
tr("All Save States (*.sav *.s##);; All Files (*)"));
State::LoadAs(path.toStdString());
}
void MainWindow::StateSave()
{
QString path = QFileDialog::getSaveFileName(this, tr("Select a File"), QDir::currentPath(),
tr("All Save States (*.sav *.s##);; All Files (*)"));
State::SaveAs(path.toStdString());
}
void MainWindow::StateLoadSlot()
{
State::Load(m_state_slot);
}
void MainWindow::StateSaveSlot()
{
State::Save(m_state_slot, true);
m_menu_bar->UpdateStateSlotMenu();
}
void MainWindow::StateLoadSlotAt(int slot)
{
State::Load(slot);
}
void MainWindow::StateSaveSlotAt(int slot)
{
State::Save(slot, true);
m_menu_bar->UpdateStateSlotMenu();
}
void MainWindow::StateLoadUndo()
{
State::UndoLoadState();
}
void MainWindow::StateSaveUndo()
{
State::UndoSaveState();
}
void MainWindow::StateSaveOldest()
{
State::SaveFirstSaved();
}
void MainWindow::SetStateSlot(int slot)
{
Settings::Instance().SetStateSlot(slot);
m_state_slot = slot;
}