diff options
| author | spycrab <spycrab@users.noreply.github.com> | 2018-07-07 00:40:15 +0200 |
|---|---|---|
| committer | spycrab <spycrab@users.noreply.github.com> | 2018-07-07 00:48:38 +0200 |
| commit | 13ba24c5a6795532c8893eccf2a21ae95682e1b7 (patch) | |
| tree | 747e394036c189c46add1b3e1451e50f48230e11 /Source/Core/DolphinQt/MainWindow.cpp | |
| parent | 059880bb1640d5a3b0e1fbbf66e1a356df4b9f41 (diff) | |
Move DolphinQt2 to DolphinQt
Diffstat (limited to 'Source/Core/DolphinQt/MainWindow.cpp')
| -rw-r--r-- | Source/Core/DolphinQt/MainWindow.cpp | 1465 |
1 files changed, 1465 insertions, 0 deletions
diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp new file mode 100644 index 0000000000..b4883368af --- /dev/null +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -0,0 +1,1465 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <QApplication> +#include <QCloseEvent> +#include <QDateTime> +#include <QDir> +#include <QDragEnterEvent> +#include <QDropEvent> +#include <QFileDialog> +#include <QFileInfo> +#include <QIcon> +#include <QMessageBox> +#include <QMimeData> +#include <QProgressDialog> +#include <QVBoxLayout> + +#include <future> +#include <optional> + +#if defined(__unix__) || defined(__unix) || defined(__APPLE__) +#include <signal.h> + +#include "QtUtils/SignalDaemon.h" +#endif + +#include "Common/Version.h" + +#include "Core/Boot/Boot.h" +#include "Core/BootManager.h" +#include "Core/CommonTitles.h" +#include "Core/Config/NetplaySettings.h" +#include "Core/ConfigManager.h" +#include "Core/Core.h" +#include "Core/HW/DVD/DVDInterface.h" +#include "Core/HW/GCKeyboard.h" +#include "Core/HW/GCPad.h" +#include "Core/HW/ProcessorInterface.h" +#include "Core/HW/Wiimote.h" +#include "Core/HW/WiimoteEmu/WiimoteEmu.h" +#include "Core/HotkeyManager.h" +#include "Core/IOS/USB/Bluetooth/BTEmu.h" +#include "Core/IOS/USB/Bluetooth/WiimoteDevice.h" +#include "Core/Movie.h" +#include "Core/NetPlayClient.h" +#include "Core/NetPlayProto.h" +#include "Core/NetPlayServer.h" +#include "Core/State.h" + +#include "DiscIO/NANDImporter.h" + +#include "DolphinQt/AboutDialog.h" +#include "DolphinQt/CheatsManager.h" +#include "DolphinQt/Config/ControllersWindow.h" +#include "DolphinQt/Config/Graphics/GraphicsWindow.h" +#include "DolphinQt/Config/LogConfigWidget.h" +#include "DolphinQt/Config/LogWidget.h" +#include "DolphinQt/Config/Mapping/MappingWindow.h" +#include "DolphinQt/Config/SettingsWindow.h" +#include "DolphinQt/Debugger/BreakpointWidget.h" +#include "DolphinQt/Debugger/CodeViewWidget.h" +#include "DolphinQt/Debugger/CodeWidget.h" +#include "DolphinQt/Debugger/JITWidget.h" +#include "DolphinQt/Debugger/MemoryWidget.h" +#include "DolphinQt/Debugger/RegisterWidget.h" +#include "DolphinQt/Debugger/WatchWidget.h" +#include "DolphinQt/FIFO/FIFOPlayerWindow.h" +#include "DolphinQt/GCMemcardManager.h" +#include "DolphinQt/Host.h" +#include "DolphinQt/HotkeyScheduler.h" +#include "DolphinQt/MainWindow.h" +#include "DolphinQt/NetPlay/NetPlayDialog.h" +#include "DolphinQt/NetPlay/NetPlaySetupDialog.h" +#include "DolphinQt/QtUtils/QueueOnObject.h" +#include "DolphinQt/QtUtils/RunOnObject.h" +#include "DolphinQt/QtUtils/WindowActivationEventFilter.h" +#include "DolphinQt/Resources.h" +#include "DolphinQt/SearchBar.h" +#include "DolphinQt/Settings.h" +#include "DolphinQt/TAS/GCTASInputWindow.h" +#include "DolphinQt/TAS/WiiTASInputWindow.h" +#include "DolphinQt/WiiUpdate.h" + +#include "InputCommon/ControllerInterface/ControllerInterface.h" + +#include "UICommon/DiscordPresence.h" +#include "UICommon/UICommon.h" + +#include "VideoCommon/VideoConfig.h" + +#if defined(HAVE_XRANDR) && HAVE_XRANDR +#include <qpa/qplatformnativeinterface.h> +#include "UICommon/X11Utils.h" +#endif + +#if defined(__unix__) || defined(__unix) || defined(__APPLE__) +void MainWindow::OnSignal() +{ + close(); +} + +static void InstallSignalHandler() +{ + struct sigaction sa; + sa.sa_handler = &SignalDaemon::HandleInterrupt; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESETHAND; + sigaction(SIGINT, &sa, nullptr); + sigaction(SIGTERM, &sa, nullptr); +} +#endif + +MainWindow::MainWindow(std::unique_ptr<BootParameters> boot_parameters) : QMainWindow(nullptr) +{ + setWindowTitle(QString::fromStdString(Common::scm_rev_str)); + setWindowIcon(Resources::GetAppIcon()); + setUnifiedTitleAndToolBarOnMac(true); + setAcceptDrops(true); + + InitControllers(); + + CreateComponents(); + + ConnectGameList(); + ConnectHost(); + ConnectToolBar(); + ConnectRenderWidget(); + ConnectStack(); + ConnectMenuBar(); + ConnectHotkeys(); + + InitCoreCallbacks(); + + NetPlayInit(); + +#if defined(__unix__) || defined(__unix) || defined(__APPLE__) + auto* daemon = new SignalDaemon(this); + + connect(daemon, &SignalDaemon::InterruptReceived, this, &MainWindow::OnSignal); + + InstallSignalHandler(); +#endif + + if (boot_parameters) + m_pending_boot = std::move(boot_parameters); + + QSettings& settings = Settings::GetQSettings(); + + restoreState(settings.value(QStringLiteral("mainwindow/state")).toByteArray()); + restoreGeometry(settings.value(QStringLiteral("mainwindow/geometry")).toByteArray()); + + m_render_widget_geometry = settings.value(QStringLiteral("renderwidget/geometry")).toByteArray(); +} + +MainWindow::~MainWindow() +{ + m_render_widget->deleteLater(); + m_netplay_dialog->deleteLater(); + ShutdownControllers(); + + QSettings& settings = Settings::GetQSettings(); + + settings.setValue(QStringLiteral("mainwindow/state"), saveState()); + settings.setValue(QStringLiteral("mainwindow/geometry"), saveGeometry()); + + settings.setValue(QStringLiteral("renderwidget/geometry"), m_render_widget_geometry); + + Config::Save(); +} + +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(); + + // Defaults won't work reliabily without loading and saving the config first + + Wiimote::LoadConfig(); + Wiimote::GetConfig()->SaveConfig(); + + Pad::LoadConfig(); + Pad::GetConfig()->SaveConfig(); + + Keyboard::LoadConfig(); + Keyboard::GetConfig()->SaveConfig(); +} + +void MainWindow::ShutdownControllers() +{ + m_hotkey_scheduler->Stop(); + + g_controller_interface.Shutdown(); + Pad::Shutdown(); + Keyboard::Shutdown(); + Wiimote::Shutdown(); + HotkeyManagerEmu::Shutdown(); + + m_hotkey_scheduler->deleteLater(); +} + +void MainWindow::InitCoreCallbacks() +{ + connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [=](Core::State state) { + if (state == Core::State::Uninitialized) + OnStopComplete(); + + if (state == Core::State::Running && m_fullscreen_requested) + { + FullScreen(); + m_fullscreen_requested = false; + } + }); + installEventFilter(this); + m_render_widget->installEventFilter(this); +} + +static void InstallHotkeyFilter(QWidget* dialog) +{ + auto* filter = new WindowActivationEventFilter(); + dialog->installEventFilter(filter); + + filter->connect(filter, &WindowActivationEventFilter::windowDeactivated, + [] { HotkeyManagerEmu::Enable(true); }); + filter->connect(filter, &WindowActivationEventFilter::windowActivated, + [] { HotkeyManagerEmu::Enable(false); }); +} + +void MainWindow::CreateComponents() +{ + m_menu_bar = new MenuBar(this); + m_tool_bar = new ToolBar(this); + m_search_bar = new SearchBar(this); + m_game_list = new GameList(this); + m_render_widget = new RenderWidget; + m_stack = new QStackedWidget(this); + m_controllers_window = new ControllersWindow(this); + m_settings_window = new SettingsWindow(this); + + for (int i = 0; i < 4; i++) + { + m_gc_tas_input_windows[i] = new GCTASInputWindow(this, i); + m_wii_tas_input_windows[i] = new WiiTASInputWindow(this, i); + } + + Movie::SetGCInputManip([this](GCPadStatus* pad_status, int controller_id) { + m_gc_tas_input_windows[controller_id]->GetValues(pad_status); + }); + + Movie::SetWiiInputManip([this](u8* input_data, WiimoteEmu::ReportFeatures rptf, int controller_id, + int ext, wiimote_key key) { + m_wii_tas_input_windows[controller_id]->GetValues(input_data, rptf, ext, key); + }); + + m_jit_widget = new JITWidget(this); + m_log_widget = new LogWidget(this); + m_log_config_widget = new LogConfigWidget(this); + m_fifo_window = new FIFOPlayerWindow(this); + m_memory_widget = new MemoryWidget(this); + + connect(m_fifo_window, &FIFOPlayerWindow::LoadFIFORequested, this, + [this](const QString& path) { StartGame(path); }); + m_register_widget = new RegisterWidget(this); + m_watch_widget = new WatchWidget(this); + m_breakpoint_widget = new BreakpointWidget(this); + m_code_widget = new CodeWidget(this); + m_cheats_manager = new CheatsManager(this); + + connect(m_watch_widget, &WatchWidget::RequestMemoryBreakpoint, + [this](u32 addr) { m_breakpoint_widget->AddAddressMBP(addr); }); + connect(m_register_widget, &RegisterWidget::RequestMemoryBreakpoint, + [this](u32 addr) { m_breakpoint_widget->AddAddressMBP(addr); }); + + connect(m_code_widget, &CodeWidget::BreakpointsChanged, m_breakpoint_widget, + &BreakpointWidget::Update); + connect(m_code_widget, &CodeWidget::RequestPPCComparison, m_jit_widget, &JITWidget::Compare); + connect(m_memory_widget, &MemoryWidget::BreakpointsChanged, m_breakpoint_widget, + &BreakpointWidget::Update); + + connect(m_breakpoint_widget, &BreakpointWidget::BreakpointsChanged, m_code_widget, + &CodeWidget::Update); + connect(m_breakpoint_widget, &BreakpointWidget::BreakpointsChanged, m_memory_widget, + &MemoryWidget::Update); + connect(m_breakpoint_widget, &BreakpointWidget::SelectedBreakpoint, [this](u32 address) { + if (Core::GetState() == Core::State::Paused) + m_code_widget->SetAddress(address, CodeViewWidget::SetAddressUpdate::WithUpdate); + }); + +#if defined(HAVE_XRANDR) && HAVE_XRANDR + m_xrr_config = std::make_unique<X11Utils::XRRConfiguration>( + static_cast<Display*>(QGuiApplication::platformNativeInterface()->nativeResourceForWindow( + "display", windowHandle())), + winId()); + m_graphics_window = new GraphicsWindow(m_xrr_config.get(), this); +#else + m_graphics_window = new GraphicsWindow(nullptr, this); +#endif + + InstallHotkeyFilter(m_controllers_window); + InstallHotkeyFilter(m_settings_window); + InstallHotkeyFilter(m_graphics_window); +} + +void MainWindow::ConnectMenuBar() +{ + setMenuBar(m_menu_bar); + // File + connect(m_menu_bar, &MenuBar::Open, this, &MainWindow::Open); + connect(m_menu_bar, &MenuBar::Exit, this, &MainWindow::close); + connect(m_menu_bar, &MenuBar::EjectDisc, this, &MainWindow::EjectDisc); + connect(m_menu_bar, &MenuBar::ChangeDisc, this, &MainWindow::ChangeDisc); + connect(m_menu_bar, &MenuBar::BootDVDBackup, this, + [this](const QString& drive) { StartGame(drive); }); + + // Emulation + connect(m_menu_bar, &MenuBar::Pause, this, &MainWindow::Pause); + connect(m_menu_bar, &MenuBar::Play, this, [this]() { Play(); }); + connect(m_menu_bar, &MenuBar::Stop, this, &MainWindow::RequestStop); + 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::Configure, this, &MainWindow::ShowSettingsWindow); + connect(m_menu_bar, &MenuBar::ConfigureGraphics, this, &MainWindow::ShowGraphicsWindow); + connect(m_menu_bar, &MenuBar::ConfigureAudio, this, &MainWindow::ShowAudioWindow); + connect(m_menu_bar, &MenuBar::ConfigureControllers, this, &MainWindow::ShowControllersWindow); + connect(m_menu_bar, &MenuBar::ConfigureHotkeys, this, &MainWindow::ShowHotkeyDialog); + + // Tools + connect(m_menu_bar, &MenuBar::ShowMemcardManager, this, &MainWindow::ShowMemcardManager); + connect(m_menu_bar, &MenuBar::ShowCheatsManager, this, &MainWindow::ShowCheatsManager); + connect(m_menu_bar, &MenuBar::BootGameCubeIPL, this, &MainWindow::OnBootGameCubeIPL); + connect(m_menu_bar, &MenuBar::ImportNANDBackup, this, &MainWindow::OnImportNANDBackup); + connect(m_menu_bar, &MenuBar::PerformOnlineUpdate, this, &MainWindow::PerformOnlineUpdate); + connect(m_menu_bar, &MenuBar::BootWiiSystemMenu, this, &MainWindow::BootWiiSystemMenu); + connect(m_menu_bar, &MenuBar::StartNetPlay, this, &MainWindow::ShowNetPlaySetupDialog); + connect(m_menu_bar, &MenuBar::ShowFIFOPlayer, this, &MainWindow::ShowFIFOPlayer); + connect(m_menu_bar, &MenuBar::ConnectWiiRemote, this, &MainWindow::OnConnectWiiRemote); + + // Movie + connect(m_menu_bar, &MenuBar::PlayRecording, this, &MainWindow::OnPlayRecording); + connect(m_menu_bar, &MenuBar::StartRecording, this, &MainWindow::OnStartRecording); + connect(m_menu_bar, &MenuBar::StopRecording, this, &MainWindow::OnStopRecording); + connect(m_menu_bar, &MenuBar::ExportRecording, this, &MainWindow::OnExportRecording); + connect(m_menu_bar, &MenuBar::ShowTASInput, this, &MainWindow::ShowTASInput); + + // View + connect(m_menu_bar, &MenuBar::ShowList, m_game_list, &GameList::SetListView); + connect(m_menu_bar, &MenuBar::ShowGrid, m_game_list, &GameList::SetGridView); + connect(m_menu_bar, &MenuBar::ToggleSearch, m_search_bar, &SearchBar::Toggle); + + connect(m_menu_bar, &MenuBar::ColumnVisibilityToggled, m_game_list, + &GameList::OnColumnVisibilityToggled); + + connect(m_menu_bar, &MenuBar::GameListPlatformVisibilityToggled, m_game_list, + &GameList::OnGameListVisibilityChanged); + connect(m_menu_bar, &MenuBar::GameListRegionVisibilityToggled, m_game_list, + &GameList::OnGameListVisibilityChanged); + + connect(m_menu_bar, &MenuBar::ShowAboutDialog, this, &MainWindow::ShowAboutDialog); + + connect(m_game_list, &GameList::SelectionChanged, m_menu_bar, &MenuBar::SelectionChanged); + connect(this, &MainWindow::ReadOnlyModeChanged, m_menu_bar, &MenuBar::ReadOnlyModeChanged); + connect(this, &MainWindow::RecordingStatusChanged, m_menu_bar, &MenuBar::RecordingStatusChanged); + + // Symbols + connect(m_menu_bar, &MenuBar::NotifySymbolsUpdated, [this] { + m_code_widget->UpdateSymbols(); + m_code_widget->Update(); + }); +} + +void MainWindow::ConnectHotkeys() +{ + connect(m_hotkey_scheduler, &HotkeyScheduler::Open, this, &MainWindow::Open); + connect(m_hotkey_scheduler, &HotkeyScheduler::ChangeDisc, this, &MainWindow::ChangeDisc); + connect(m_hotkey_scheduler, &HotkeyScheduler::EjectDisc, this, &MainWindow::EjectDisc); + connect(m_hotkey_scheduler, &HotkeyScheduler::ExitHotkey, this, &MainWindow::close); + connect(m_hotkey_scheduler, &HotkeyScheduler::TogglePauseHotkey, this, &MainWindow::TogglePause); + connect(m_hotkey_scheduler, &HotkeyScheduler::RefreshGameListHotkey, this, + &MainWindow::RefreshGameList); + connect(m_hotkey_scheduler, &HotkeyScheduler::StopHotkey, this, &MainWindow::RequestStop); + connect(m_hotkey_scheduler, &HotkeyScheduler::ResetHotkey, this, &MainWindow::Reset); + connect(m_hotkey_scheduler, &HotkeyScheduler::ScreenShotHotkey, this, &MainWindow::ScreenShot); + connect(m_hotkey_scheduler, &HotkeyScheduler::FullScreenHotkey, this, &MainWindow::FullScreen); + + connect(m_hotkey_scheduler, &HotkeyScheduler::StateLoadSlot, this, &MainWindow::StateLoadSlotAt); + connect(m_hotkey_scheduler, &HotkeyScheduler::StateSaveSlot, this, &MainWindow::StateSaveSlotAt); + connect(m_hotkey_scheduler, &HotkeyScheduler::StateLoadLastSaved, this, + &MainWindow::StateLoadLastSavedAt); + connect(m_hotkey_scheduler, &HotkeyScheduler::StateLoadUndo, this, &MainWindow::StateLoadUndo); + connect(m_hotkey_scheduler, &HotkeyScheduler::StateSaveUndo, this, &MainWindow::StateSaveUndo); + connect(m_hotkey_scheduler, &HotkeyScheduler::StateSaveOldest, this, + &MainWindow::StateSaveOldest); + connect(m_hotkey_scheduler, &HotkeyScheduler::StateSaveFile, this, &MainWindow::StateSave); + connect(m_hotkey_scheduler, &HotkeyScheduler::StateLoadFile, this, &MainWindow::StateLoad); + + 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); + connect(m_hotkey_scheduler, &HotkeyScheduler::StartRecording, this, + &MainWindow::OnStartRecording); + connect(m_hotkey_scheduler, &HotkeyScheduler::ExportRecording, this, + &MainWindow::OnExportRecording); + connect(m_hotkey_scheduler, &HotkeyScheduler::ConnectWiiRemote, this, + &MainWindow::OnConnectWiiRemote); + connect(m_hotkey_scheduler, &HotkeyScheduler::ToggleReadOnlyMode, [this] { + bool read_only = !Movie::IsReadOnly(); + Movie::SetReadOnly(read_only); + emit ReadOnlyModeChanged(read_only); + }); + + connect(m_hotkey_scheduler, &HotkeyScheduler::Step, m_code_widget, &CodeWidget::Step); + connect(m_hotkey_scheduler, &HotkeyScheduler::StepOver, m_code_widget, &CodeWidget::StepOver); + connect(m_hotkey_scheduler, &HotkeyScheduler::StepOut, m_code_widget, &CodeWidget::StepOut); + connect(m_hotkey_scheduler, &HotkeyScheduler::Skip, m_code_widget, &CodeWidget::Skip); + + connect(m_hotkey_scheduler, &HotkeyScheduler::ShowPC, m_code_widget, &CodeWidget::ShowPC); + connect(m_hotkey_scheduler, &HotkeyScheduler::SetPC, m_code_widget, &CodeWidget::SetPC); + + connect(m_hotkey_scheduler, &HotkeyScheduler::ToggleBreakpoint, m_code_widget, + &CodeWidget::ToggleBreakpoint); + connect(m_hotkey_scheduler, &HotkeyScheduler::AddBreakpoint, m_code_widget, + &CodeWidget::AddBreakpoint); +} + +void MainWindow::ConnectToolBar() +{ + addToolBar(m_tool_bar); + + connect(m_tool_bar, &ToolBar::OpenPressed, this, &MainWindow::Open); + connect(m_tool_bar, &ToolBar::RefreshPressed, this, &MainWindow::RefreshGameList); + + connect(m_tool_bar, &ToolBar::PlayPressed, this, [this]() { Play(); }); + connect(m_tool_bar, &ToolBar::PausePressed, this, &MainWindow::Pause); + connect(m_tool_bar, &ToolBar::StopPressed, this, &MainWindow::RequestStop); + 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); + connect(m_tool_bar, &ToolBar::ControllersPressed, this, &MainWindow::ShowControllersWindow); + connect(m_tool_bar, &ToolBar::GraphicsPressed, this, &MainWindow::ShowGraphicsWindow); + + connect(m_tool_bar, &ToolBar::StepPressed, m_code_widget, &CodeWidget::Step); + connect(m_tool_bar, &ToolBar::StepOverPressed, m_code_widget, &CodeWidget::StepOver); + connect(m_tool_bar, &ToolBar::StepOutPressed, m_code_widget, &CodeWidget::StepOut); + connect(m_tool_bar, &ToolBar::SkipPressed, m_code_widget, &CodeWidget::Skip); + connect(m_tool_bar, &ToolBar::ShowPCPressed, m_code_widget, &CodeWidget::ShowPC); + connect(m_tool_bar, &ToolBar::SetPCPressed, m_code_widget, &CodeWidget::SetPC); +} + +void MainWindow::ConnectGameList() +{ + connect(m_game_list, &GameList::GameSelected, this, [this]() { Play(); }); + connect(m_game_list, &GameList::NetPlayHost, this, &MainWindow::NetPlayHost); + + connect(m_game_list, &GameList::OpenGeneralSettings, this, &MainWindow::ShowGeneralWindow); +} + +void MainWindow::ConnectRenderWidget() +{ + m_rendering_to_main = false; + m_render_widget->hide(); + connect(m_render_widget, &RenderWidget::Closed, this, &MainWindow::ForceStop); + connect(m_render_widget, &RenderWidget::FocusChanged, this, [this](bool focus) { + if (m_render_widget->isFullScreen()) + SetFullScreenResolution(focus); + }); +} + +void MainWindow::ConnectHost() +{ + connect(Host::GetInstance(), &Host::UpdateProgressDialog, this, + &MainWindow::OnUpdateProgressDialog); +} + +void MainWindow::ConnectStack() +{ + auto* widget = new QWidget; + auto* layout = new QVBoxLayout; + widget->setLayout(layout); + + layout->addWidget(m_game_list); + layout->addWidget(m_search_bar); + layout->setMargin(0); + + connect(m_search_bar, &SearchBar::Search, m_game_list, &GameList::SetSearchTerm); + + m_stack->addWidget(widget); + + setCentralWidget(m_stack); + + setTabPosition(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea, QTabWidget::North); + addDockWidget(Qt::LeftDockWidgetArea, m_log_widget); + addDockWidget(Qt::LeftDockWidgetArea, m_log_config_widget); + addDockWidget(Qt::LeftDockWidgetArea, m_code_widget); + addDockWidget(Qt::LeftDockWidgetArea, m_register_widget); + addDockWidget(Qt::LeftDockWidgetArea, m_watch_widget); + addDockWidget(Qt::LeftDockWidgetArea, m_breakpoint_widget); + addDockWidget(Qt::LeftDockWidgetArea, m_memory_widget); + addDockWidget(Qt::LeftDockWidgetArea, m_jit_widget); + + tabifyDockWidget(m_log_widget, m_log_config_widget); + tabifyDockWidget(m_log_widget, m_code_widget); + tabifyDockWidget(m_log_widget, m_register_widget); + tabifyDockWidget(m_log_widget, m_watch_widget); + tabifyDockWidget(m_log_widget, m_breakpoint_widget); + tabifyDockWidget(m_log_widget, m_memory_widget); + tabifyDockWidget(m_log_widget, m_jit_widget); +} + +void MainWindow::RefreshGameList() +{ + Settings::Instance().ReloadTitleDB(); + Settings::Instance().RefreshGameList(); +} + +QString MainWindow::PromptFileName() +{ + auto& settings = Settings::Instance().GetQSettings(); + QString path = QFileDialog::getOpenFileName( + this, tr("Select a File"), + settings.value(QStringLiteral("mainwindow/lastdir"), QStringLiteral("")).toString(), + tr("All GC/Wii files (*.elf *.dol *.gcm *.iso *.tgc *.wbfs *.ciso *.gcz *.wad);;" + "All Files (*)")); + + if (!path.isEmpty()) + { + settings.setValue(QStringLiteral("mainwindow/lastdir"), + QFileInfo(path).absoluteDir().absolutePath()); + } + + return path; +} + +void MainWindow::ChangeDisc() +{ + QString file = PromptFileName(); + + if (!file.isEmpty()) + Core::RunAsCPUThread([&file] { DVDInterface::ChangeDisc(file.toStdString()); }); +} + +void MainWindow::EjectDisc() +{ + Core::RunAsCPUThread(DVDInterface::EjectDisc); +} + +void MainWindow::Open() +{ + QString file = PromptFileName(); + if (!file.isEmpty()) + StartGame(file); +} + +void MainWindow::Play(const std::optional<std::string>& savestate_path) +{ + // 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); + } + else + { + std::shared_ptr<const UICommon::GameFile> selection = m_game_list->GetSelectedGame(); + if (selection) + { + StartGame(selection->GetFilePath(), savestate_path); + EnableScreenSaver(false); + } + else + { + QString default_path = QString::fromStdString(SConfig::GetInstance().m_strDefaultISO); + if (!default_path.isEmpty() && QFile::exists(default_path)) + { + StartGame(default_path, savestate_path); + EnableScreenSaver(false); + } + else + { + Open(); + } + } + } +} + +void MainWindow::Pause() +{ + Core::SetState(Core::State::Paused); +} + +void MainWindow::TogglePause() +{ + if (Core::GetState() == Core::State::Paused) + { + Play(); + } + else + { + Pause(); + } +} + +void MainWindow::OnStopComplete() +{ + m_stop_requested = false; + HideRenderWidget(); + EnableScreenSaver(true); +#ifdef USE_DISCORD_PRESENCE + Discord::UpdateDiscordPresence(); +#endif + + SetFullScreenResolution(false); + + if (m_exit_requested || Settings::Instance().IsBatchModeEnabled()) + QGuiApplication::instance()->quit(); + + // If the current emulation prevented the booting of another, do that now + if (m_pending_boot != nullptr) + { + StartGame(std::move(m_pending_boot)); + m_pending_boot.reset(); + } +} + +bool MainWindow::RequestStop() +{ + if (!Core::IsRunning()) + { + Core::QueueHostJob([this] { OnStopComplete(); }, true); + return true; + } + + if (!m_render_widget->isFullScreen()) + m_render_widget_geometry = m_render_widget->saveGeometry(); + else + FullScreen(); + + if (SConfig::GetInstance().bConfirmStop) + { + const Core::State state = Core::GetState(); + + // Only pause the game, if NetPlay is not running + bool pause = Settings::Instance().GetNetPlayClient() == nullptr; + + if (pause) + Core::SetState(Core::State::Paused); + + QMessageBox::StandardButton confirm; + confirm = QMessageBox::question(this, tr("Confirm"), + m_stop_requested ? + tr("A shutdown is already in progress. Unsaved data " + "may be lost if you stop the current emulation " + "before it completes. Force stop?") : + tr("Do you want to stop the current emulation?")); + + if (pause) + Core::SetState(state); + + if (confirm != QMessageBox::Yes) + return false; + } + + // TODO: Add Movie shutdown + // TODO: Add Debugger shutdown + + if (!m_stop_requested && UICommon::TriggerSTMPowerEvent()) + { + m_stop_requested = true; + + // Unpause because gracefully shutting down needs the game to actually request a shutdown. + // TODO: Do not unpause in debug mode to allow debugging until the complete shutdown. + if (Core::GetState() == Core::State::Paused) + Core::SetState(Core::State::Running); + + return true; + } + + ForceStop(); +#ifdef Q_OS_WIN + // Allow windows to idle or turn off display again + SetThreadExecutionState(ES_CONTINUOUS); +#endif + return true; +} + +void MainWindow::ForceStop() +{ + Core::Stop(); +} + +void MainWindow::Reset() +{ + if (Movie::IsRecordingInput()) + Movie::SetReset(true); + ProcessorInterface::ResetButton_Tap(); +} + +void MainWindow::FrameAdvance() +{ + Core::DoFrameStep(); +} + +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(); + + if (!was_fullscreen) + m_render_widget_geometry = m_render_widget->saveGeometry(); + + HideRenderWidget(false); + SetFullScreenResolution(!was_fullscreen); + + if (was_fullscreen) + { + ShowRenderWidget(); + } + else + { + m_render_widget->showFullScreen(); + } +} + +void MainWindow::ScreenShot() +{ + Core::SaveScreenShot(); +} + +void MainWindow::StartGame(const QString& path, const std::optional<std::string>& savestate_path) +{ + StartGame(path.toStdString(), savestate_path); +} + +void MainWindow::StartGame(const std::string& path, + const std::optional<std::string>& savestate_path) +{ + StartGame(BootParameters::GenerateFromFile(path, savestate_path)); +} + +void MainWindow::StartGame(std::unique_ptr<BootParameters>&& parameters) +{ + // If we're running, only start a new game once we've stopped the last. + if (Core::GetState() != Core::State::Uninitialized) + { + if (!RequestStop()) + return; + + // As long as the shutdown isn't complete, we can't boot, so let's boot later + m_pending_boot = std::move(parameters); + return; + } + // Boot up, show an error if it fails to load the game. + if (!BootManager::BootCore(std::move(parameters))) + { + QMessageBox::critical(this, tr("Error"), tr("Failed to init core"), QMessageBox::Ok); + return; + } + + ShowRenderWidget(); +#ifdef USE_DISCORD_PRESENCE + Discord::UpdateDiscordPresence(); +#endif + + if (SConfig::GetInstance().bFullscreen) + m_fullscreen_requested = true; + +#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 +} + +void MainWindow::SetFullScreenResolution(bool fullscreen) +{ + if (SConfig::GetInstance().strFullscreenResolution == "Auto") + return; +#ifdef _WIN32 + + if (!fullscreen) + { + ChangeDisplaySettings(nullptr, CDS_FULLSCREEN); + return; + } + + DEVMODE screen_settings; + memset(&screen_settings, 0, sizeof(screen_settings)); + screen_settings.dmSize = sizeof(screen_settings); + sscanf(SConfig::GetInstance().strFullscreenResolution.c_str(), "%dx%d", + &screen_settings.dmPelsWidth, &screen_settings.dmPelsHeight); + screen_settings.dmBitsPerPel = 32; + screen_settings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; + + // Try To Set Selected Mode And Get Results. NOTE: CDS_FULLSCREEN Gets Rid Of Start Bar. + ChangeDisplaySettings(&screen_settings, CDS_FULLSCREEN); +#elif defined(HAVE_XRANDR) && HAVE_XRANDR + m_xrr_config->ToggleDisplayMode(fullscreen); +#endif +} + +void MainWindow::ShowRenderWidget() +{ + SetFullScreenResolution(false); + Host::GetInstance()->SetRenderFullscreen(false); + + if (SConfig::GetInstance().bRenderToMain) + { + // 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); + m_stack->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + m_stack->repaint(); + + Host::GetInstance()->SetRenderFocus(isActiveWindow()); + } + else + { + // Otherwise, just show it. + m_rendering_to_main = false; + + m_render_widget->showNormal(); + m_render_widget->restoreGeometry(m_render_widget_geometry); + } +} + +void MainWindow::HideRenderWidget(bool reinit) +{ + 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; + m_stack->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + disconnect(Host::GetInstance(), &Host::RequestTitle, this, &MainWindow::setWindowTitle); + setWindowTitle(QString::fromStdString(Common::scm_rev_str)); + } + + // The following code works around a driver bug that would lead to Dolphin crashing when changing + // graphics backends (e.g. OpenGL to Vulkan). To avoid this the render widget is (safely) + // recreated + if (reinit) + { + m_render_widget->hide(); + disconnect(m_render_widget, &RenderWidget::Closed, this, &MainWindow::ForceStop); + + m_render_widget->removeEventFilter(this); + m_render_widget->deleteLater(); + + m_render_widget = new RenderWidget; + + m_render_widget->installEventFilter(this); + connect(m_render_widget, &RenderWidget::Closed, this, &MainWindow::ForceStop); + connect(m_render_widget, &RenderWidget::FocusChanged, this, [this](bool focus) { + if (m_render_widget->isFullScreen()) + SetFullScreenResolution(focus); + }); + } +} + +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::ShowAudioWindow() +{ + m_settings_window->SelectAudioPane(); + ShowSettingsWindow(); +} + +void MainWindow::ShowGeneralWindow() +{ + m_settings_window->SelectGeneralPane(); + ShowSettingsWindow(); +} + +void MainWindow::ShowAboutDialog() +{ + AboutDialog about{this}; + about.exec(); +} + +void MainWindow::ShowHotkeyDialog() +{ + auto* hotkey_window = new MappingWindow(this, MappingWindow::Type::MAPPING_HOTKEYS, 0); + + InstallHotkeyFilter(hotkey_window); + + hotkey_window->show(); + hotkey_window->raise(); + hotkey_window->activateWindow(); +} + +void MainWindow::ShowGraphicsWindow() +{ + m_graphics_window->Initialize(); + m_graphics_window->show(); + m_graphics_window->raise(); + m_graphics_window->activateWindow(); +} + +void MainWindow::ShowNetPlaySetupDialog() +{ + m_netplay_setup_dialog->show(); + m_netplay_setup_dialog->raise(); + m_netplay_setup_dialog->activateWindow(); +} + +void MainWindow::ShowFIFOPlayer() +{ + m_fifo_window->show(); + m_fifo_window->raise(); + m_fifo_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::StateLoadLastSavedAt(int slot) +{ + State::LoadLastSaved(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; + + Core::DisplayMessage(StringFromFormat("Selected slot %d - %s", m_state_slot, + State::GetInfoStringOfSlot(m_state_slot, false).c_str()), + 2500); +} + +void MainWindow::PerformOnlineUpdate(const std::string& region) +{ + WiiUpdate::PerformOnlineUpdate(region, this); + // Since the update may have installed a newer system menu, refresh the tools menu. + m_menu_bar->UpdateToolsMenu(false); +} + +void MainWindow::BootWiiSystemMenu() +{ + StartGame(std::make_unique<BootParameters>(BootParameters::NANDTitle{Titles::SYSTEM_MENU})); +} + +void MainWindow::NetPlayInit() +{ + m_netplay_setup_dialog = new NetPlaySetupDialog(this); + m_netplay_dialog = new NetPlayDialog; + + connect(m_netplay_dialog, &NetPlayDialog::Boot, this, + [this](const QString& path) { StartGame(path); }); + connect(m_netplay_dialog, &NetPlayDialog::Stop, this, &MainWindow::ForceStop); + connect(m_netplay_dialog, &NetPlayDialog::rejected, this, &MainWindow::NetPlayQuit); + connect(m_netplay_setup_dialog, &NetPlaySetupDialog::Join, this, &MainWindow::NetPlayJoin); + connect(m_netplay_setup_dialog, &NetPlaySetupDialog::Host, this, &MainWindow::NetPlayHost); +} + +bool MainWindow::NetPlayJoin() +{ + if (Core::IsRunning()) + { + QMessageBox::critical( + nullptr, QObject::tr("Error"), + QObject::tr("Can't start a NetPlay Session while a game is still running!")); + return false; + } + + if (m_netplay_dialog->isVisible()) + { + QMessageBox::critical(nullptr, QObject::tr("Error"), + QObject::tr("A NetPlay Session is already in progress!")); + return false; + } + + // Settings + const std::string traversal_choice = Config::Get(Config::NETPLAY_TRAVERSAL_CHOICE); + const bool is_traversal = traversal_choice == "traversal"; + + std::string host_ip; + u16 host_port; + if (Settings::Instance().GetNetPlayServer() != nullptr) + { + host_ip = "127.0.0.1"; + host_port = Settings::Instance().GetNetPlayServer()->GetPort(); + } + else + { + host_ip = is_traversal ? Config::Get(Config::NETPLAY_HOST_CODE) : + Config::Get(Config::NETPLAY_ADDRESS); + host_port = Config::Get(Config::NETPLAY_CONNECT_PORT); + } + + const std::string traversal_host = Config::Get(Config::NETPLAY_TRAVERSAL_SERVER); + const u16 traversal_port = Config::Get(Config::NETPLAY_TRAVERSAL_PORT); + const std::string nickname = Config::Get(Config::NETPLAY_NICKNAME); + + // Create Client + Settings::Instance().ResetNetPlayClient(new NetPlayClient( + host_ip, host_port, m_netplay_dialog, nickname, + NetTraversalConfig{Settings::Instance().GetNetPlayServer() != nullptr ? false : is_traversal, + traversal_host, traversal_port})); + + if (!Settings::Instance().GetNetPlayClient()->IsConnected()) + { + NetPlayQuit(); + return false; + } + + m_netplay_setup_dialog->close(); + m_netplay_dialog->show(nickname, is_traversal); + + return true; +} + +bool MainWindow::NetPlayHost(const QString& game_id) +{ + if (Core::IsRunning()) + { + QMessageBox::critical( + nullptr, QObject::tr("Error"), + QObject::tr("Can't start a NetPlay Session while a game is still running!")); + return false; + } + + if (m_netplay_dialog->isVisible()) + { + QMessageBox::critical(nullptr, QObject::tr("Error"), + QObject::tr("A NetPlay Session is already in progress!")); + return false; + } + + // Settings + u16 host_port = Config::Get(Config::NETPLAY_HOST_PORT); + const std::string traversal_choice = Config::Get(Config::NETPLAY_TRAVERSAL_CHOICE); + const bool is_traversal = traversal_choice == "traversal"; + const bool use_upnp = Config::Get(Config::NETPLAY_USE_UPNP); + + const std::string traversal_host = Config::Get(Config::NETPLAY_TRAVERSAL_SERVER); + const u16 traversal_port = Config::Get(Config::NETPLAY_TRAVERSAL_PORT); + + if (is_traversal) + host_port = Config::Get(Config::NETPLAY_LISTEN_PORT); + + // Create Server + Settings::Instance().ResetNetPlayServer(new NetPlayServer( + host_port, use_upnp, NetTraversalConfig{is_traversal, traversal_host, traversal_port})); + + if (!Settings::Instance().GetNetPlayServer()->is_connected) + { + QMessageBox::critical( + nullptr, QObject::tr("Failed to open server"), + QObject::tr( + "Failed to listen on port %1. Is another instance of the NetPlay server running?") + .arg(host_port)); + NetPlayQuit(); + return false; + } + + Settings::Instance().GetNetPlayServer()->ChangeGame(game_id.toStdString()); + + // Join our local server + return NetPlayJoin(); +} + +void MainWindow::NetPlayQuit() +{ + Settings::Instance().ResetNetPlayClient(); + Settings::Instance().ResetNetPlayServer(); +} + +void MainWindow::EnableScreenSaver(bool enable) +{ +#if defined(HAVE_XRANDR) && HAVE_XRANDR + UICommon::EnableScreenSaver(winId(), enable); +#else + UICommon::EnableScreenSaver(enable); +#endif +} + +bool MainWindow::eventFilter(QObject* object, QEvent* event) +{ + if (event->type() == QEvent::Close) + { + if (RequestStop() && object == this) + m_exit_requested = true; + + static_cast<QCloseEvent*>(event)->ignore(); + return true; + } + + return false; +} + +void MainWindow::dragEnterEvent(QDragEnterEvent* event) +{ + if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() == 1) + event->acceptProposedAction(); +} + +void MainWindow::dropEvent(QDropEvent* event) +{ + const auto& urls = event->mimeData()->urls(); + if (urls.empty()) + return; + + const auto& url = urls[0]; + QFileInfo file_info(url.toLocalFile()); + + auto path = file_info.filePath(); + + if (!file_info.exists() || !file_info.isReadable()) + { + QMessageBox::critical(this, tr("Error"), tr("Failed to open '%1'").arg(path)); + return; + } + + if (file_info.isFile()) + { + StartGame(path); + } + else + { + auto& settings = Settings::Instance(); + + if (settings.GetPaths().size() != 0) + { + if (QMessageBox::question( + this, tr("Confirm"), + tr("Do you want to add \"%1\" to the list of Game Paths?").arg(path)) != + QMessageBox::Yes) + return; + } + settings.AddPath(path); + } +} + +QSize MainWindow::sizeHint() const +{ + return QSize(800, 600); +} + +void MainWindow::OnBootGameCubeIPL(DiscIO::Region region) +{ + StartGame(std::make_unique<BootParameters>(BootParameters::IPL{region})); +} + +void MainWindow::OnImportNANDBackup() +{ + auto response = QMessageBox::question( + this, tr("Question"), + tr("Merging a new NAND over your currently selected NAND will overwrite any channels " + "and savegames that already exist. This process is not reversible, so it is " + "recommended that you keep backups of both NANDs. Are you sure you want to " + "continue?")); + + if (response == QMessageBox::No) + return; + + QString file = QFileDialog::getOpenFileName(this, tr("Select the save file"), QDir::currentPath(), + tr("BootMii NAND backup file (*.bin);;" + "All Files (*)")); + + if (file.isEmpty()) + return; + + QProgressDialog* dialog = new QProgressDialog(this); + dialog->setMinimum(0); + dialog->setMaximum(0); + dialog->setLabelText(tr("Importing NAND backup")); + dialog->setCancelButton(nullptr); + + auto beginning = QDateTime::currentDateTime().toMSecsSinceEpoch(); + + auto result = std::async(std::launch::async, [&] { + DiscIO::NANDImporter().ImportNANDBin( + file.toStdString(), + [&dialog, beginning] { + QueueOnObject(dialog, [&dialog, beginning] { + dialog->setLabelText( + tr("Importing NAND backup\n Time elapsed: %1s") + .arg((QDateTime::currentDateTime().toMSecsSinceEpoch() - beginning) / 1000)); + }); + }, + [this] { + std::optional<std::string> keys_file = RunOnObject(this, [this] { + return QFileDialog::getOpenFileName(this, tr("Select the keys file (OTP/SEEPROM dump)"), + QDir::currentPath(), + tr("BootMii keys file (*.bin);;" + "All Files (*)")) + .toStdString(); + }); + if (keys_file) + return *keys_file; + return std::string(""); + }); + QueueOnObject(dialog, &QProgressDialog::close); + }); + + dialog->exec(); + + result.wait(); + + m_menu_bar->UpdateToolsMenu(Core::IsRunning()); +} + +void MainWindow::OnPlayRecording() +{ + QString dtm_file = QFileDialog::getOpenFileName(this, tr("Select the Recording File"), QString(), + tr("Dolphin TAS Movies (*.dtm)")); + + if (dtm_file.isEmpty()) + return; + + if (!Movie::IsReadOnly()) + { + // let's make the read-only flag consistent at the start of a movie. + Movie::SetReadOnly(true); + emit ReadOnlyModeChanged(true); + } + + std::optional<std::string> savestate_path; + if (Movie::PlayInput(dtm_file.toStdString(), &savestate_path)) + { + emit RecordingStatusChanged(true); + + Play(savestate_path); + } +} + +void MainWindow::OnStartRecording() +{ + if ((!Core::IsRunningAndStarted() && Core::IsRunning()) || Movie::IsRecordingInput() || + Movie::IsPlayingInput()) + return; + + if (Movie::IsReadOnly()) + { + // The user just chose to record a movie, so that should take precedence + Movie::SetReadOnly(false); + emit ReadOnlyModeChanged(true); + } + + int controllers = 0; + + for (int i = 0; i < 4; i++) + { + if (SerialInterface::SIDevice_IsGCController(SConfig::GetInstance().m_SIDevice[i])) + controllers |= (1 << i); + + if (g_wiimote_sources[i] != WIIMOTE_SRC_NONE) + controllers |= (1 << (i + 4)); + } + + if (Movie::BeginRecordingInput(controllers)) + { + emit RecordingStatusChanged(true); + + if (!Core::IsRunning()) + Play(); + } +} + +void MainWindow::OnStopRecording() +{ + if (Movie::IsRecordingInput()) + OnExportRecording(); + + Movie::EndPlayInput(false); + emit RecordingStatusChanged(true); +} + +void MainWindow::OnExportRecording() +{ + bool was_paused = Core::GetState() == Core::State::Paused; + + if (was_paused) + Core::SetState(Core::State::Paused); + + QString dtm_file = QFileDialog::getSaveFileName(this, tr("Select the Recording File"), QString(), + tr("Dolphin TAS Movies (*.dtm)")); + + if (was_paused) + Core::SetState(Core::State::Running); + + if (dtm_file.isEmpty()) + return; + + Core::SetState(Core::State::Running); + + Movie::SaveRecording(dtm_file.toStdString()); +} + +void MainWindow::ShowTASInput() +{ + for (int i = 0; i < num_gc_controllers; i++) + { + if (SConfig::GetInstance().m_SIDevice[i] != SerialInterface::SIDEVICE_NONE && + SConfig::GetInstance().m_SIDevice[i] != SerialInterface::SIDEVICE_GC_GBA) + { + m_gc_tas_input_windows[i]->show(); + m_gc_tas_input_windows[i]->raise(); + m_gc_tas_input_windows[i]->activateWindow(); + } + } + + for (int i = 0; i < num_wii_controllers; i++) + { + if (g_wiimote_sources[i] == WIIMOTE_SRC_EMU && + (!Core::IsRunning() || SConfig::GetInstance().bWii)) + { + m_wii_tas_input_windows[i]->show(); + m_wii_tas_input_windows[i]->raise(); + m_wii_tas_input_windows[i]->activateWindow(); + } + } +} + +void MainWindow::OnConnectWiiRemote(int id) +{ + const auto ios = IOS::HLE::GetIOS(); + if (!ios || SConfig::GetInstance().m_bt_passthrough_enabled) + return; + Core::RunAsCPUThread([&] { + const auto bt = std::static_pointer_cast<IOS::HLE::Device::BluetoothEmu>( + ios->GetDeviceByName("/dev/usb/oh1/57e/305")); + const bool is_connected = bt && bt->AccessWiimoteByIndex(id)->IsConnected(); + Wiimote::Connect(id, !is_connected); + }); +} + +void MainWindow::ShowMemcardManager() +{ + GCMemcardManager manager(this); + + manager.exec(); +} + +void MainWindow::ShowCheatsManager() +{ + m_cheats_manager->show(); +} + +void MainWindow::OnUpdateProgressDialog(QString title, int progress, int total) +{ + if (!m_progress_dialog) + { + m_progress_dialog = new QProgressDialog(m_render_widget); + m_progress_dialog->show(); + } + + m_progress_dialog->setValue(progress); + m_progress_dialog->setLabelText(title); + m_progress_dialog->setWindowTitle(title); + m_progress_dialog->setMaximum(total); + + if (total < 0 || progress >= total) + { + m_progress_dialog->hide(); + m_progress_dialog->deleteLater(); + m_progress_dialog = nullptr; + } +} + +void MainWindow::Show() +{ + QWidget::show(); + + // If the booting of a game was requested on start up, do that now + if (m_pending_boot != nullptr) + { + StartGame(std::move(m_pending_boot)); + m_pending_boot.reset(); + } +} |
