Merge pull request #5810 from spycrab/qt_netplay

Qt: Implement Netplay
This commit is contained in:
Leo Lam 2017-08-10 14:45:52 +08:00 committed by GitHub
commit d0304c9b38
23 changed files with 1741 additions and 7 deletions

View File

@ -66,6 +66,11 @@ set(SRCS
GameList/GridProxyModel.cpp
GameList/ListProxyModel.cpp
QtUtils/BlockUserInputFilter.cpp
NetPlay/GameListDialog.cpp
NetPlay/MD5Dialog.cpp
NetPlay/NetPlayDialog.cpp
NetPlay/NetPlaySetupDialog.cpp
NetPlay/PadMappingDialog.cpp
QtUtils/DoubleClickEventFilter.cpp
QtUtils/ElidedButton.cpp
QtUtils/ListTabWidget.cpp

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
@ -47,7 +47,7 @@
<AdditionalDependencies>avrt.lib;iphlpapi.lib;winmm.lib;setupapi.lib;opengl32.lib;glu32.lib;rpcrt4.lib;comctl32.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<ClCompile>
<AdditionalIncludeDirectories>$(ProjectDir)VideoInterface;$(ProjectDir)GameList;$(ProjectDir)Settings;$(ProjectDir)Config;$(ProjectDir)Config\Mapping;$(ProjectDir)Config\Graphics;$(ProjectDir)QtUtils;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ProjectDir)VideoInterface;$(ProjectDir)GameList;$(ProjectDir)Settings;$(ProjectDir)Config;$(ProjectDir)Config\Mapping;$(ProjectDir)Config\Graphics;$(ProjectDir)NetPlay;$(ProjectDir)QtUtils;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Manifest>
<AdditionalManifestFiles>DolphinQt2.manifest;%(AdditionalManifestFiles)</AdditionalManifestFiles>
@ -93,6 +93,11 @@
<QtMoc Include="Settings\AudioPane.h" />
<QtMoc Include="MainWindow.h" />
<QtMoc Include="MenuBar.h" />
<QtMoc Include="NetPlay\GameListDialog.h" />
<QtMoc Include="NetPlay\MD5Dialog.h" />
<QtMoc Include="NetPlay\NetPlayDialog.h" />
<QtMoc Include="NetPlay\NetPlaySetupDialog.h" />
<QtMoc Include="NetPlay\PadMappingDialog.h" />
<QtMoc Include="QtUtils\DoubleClickEventFilter.h" />
<QtMoc Include="QtUtils\WindowActivationEventFilter.h" />
<QtMoc Include="RenderWidget.h" />
@ -110,6 +115,7 @@
<ClCompile Include="$(QtMocOutPrefix)FilesystemWidget.cpp" />
<ClCompile Include="$(QtMocOutPrefix)WindowActivationEventFilter.cpp" />
<ClCompile Include="$(QtMocOutPrefix)GameList.cpp" />
<ClCompile Include="$(QtMocOutPrefix)GameListDialog.cpp" />
<ClCompile Include="$(QtMocOutPrefix)GameListModel.cpp" />
<ClCompile Include="$(QtMocOutPrefix)GameTracker.cpp" />
<ClCompile Include="$(QtMocOutPrefix)GeneralPane.cpp" />
@ -133,7 +139,11 @@
<ClCompile Include="$(QtMocOutPrefix)MappingButton.cpp" />
<ClCompile Include="$(QtMocOutPrefix)MappingWidget.cpp" />
<ClCompile Include="$(QtMocOutPrefix)MappingWindow.cpp" />
<ClCompile Include="$(QtMocOutPrefix)MD5Dialog.cpp" />
<ClCompile Include="$(QtMocOutPrefix)MenuBar.cpp" />
<ClCompile Include="$(QtMocOutPrefix)NetPlayDialog.cpp" />
<ClCompile Include="$(QtMocOutPrefix)NetPlaySetupDialog.cpp" />
<ClCompile Include="$(QtMocOutPrefix)PadMappingDialog.cpp" />
<ClCompile Include="$(QtMocOutPrefix)PropertiesDialog.cpp" />
<ClCompile Include="$(QtMocOutPrefix)DoubleClickEventFilter.cpp" />
<ClCompile Include="$(QtMocOutPrefix)RenderWidget.cpp" />
@ -190,6 +200,11 @@
<ClCompile Include="MainWindow.cpp" />
<ClCompile Include="MenuBar.cpp" />
<ClCompile Include="QtUtils\BlockUserInputFilter.cpp" />
<ClCompile Include="NetPlay\GameListDialog.cpp" />
<ClCompile Include="NetPlay\MD5Dialog.cpp" />
<ClCompile Include="NetPlay\NetPlayDialog.cpp" />
<ClCompile Include="NetPlay\NetPlaySetupDialog.cpp" />
<ClCompile Include="NetPlay\PadMappingDialog.cpp" />
<ClCompile Include="QtUtils\DoubleClickEventFilter.cpp" />
<ClCompile Include="QtUtils\ElidedButton.cpp" />
<ClCompile Include="QtUtils\ListTabWidget.cpp" />
@ -308,4 +323,4 @@
<Message Text="Copy: @(BinaryFiles) -&gt; $(BinaryOutputDir)" Importance="High" />
<Copy SourceFiles="@(BinaryFiles)" DestinationFolder="$(BinaryOutputDir)" />
</Target>
</Project>
</Project>

View File

@ -11,6 +11,7 @@
#include "Common/Assert.h"
#include "Common/FileUtil.h"
#include "Common/NandPaths.h"
#include "Common/StringUtil.h"
#include "Core/ConfigManager.h"
#include "Core/HW/WiiSaveCrypted.h"
#include "Core/IOS/ES/ES.h"
@ -305,6 +306,51 @@ QString GameFile::GetLanguage(DiscIO::Language lang) const
}
}
QString GameFile::GetUniqueID() const
{
std::vector<std::string> info;
if (!GetGameID().isEmpty())
info.push_back(GetGameID().toStdString());
if (GetRevision() != 0)
{
info.push_back("Revision " + std::to_string(GetRevision()));
}
std::string name = m_long_names[DiscIO::Language::LANGUAGE_ENGLISH].toStdString();
if (name.empty())
{
if (!m_long_names.isEmpty())
name = m_long_names.begin().value().toStdString();
else
{
std::string filename, extension;
name = SplitPath(m_path.toStdString(), nullptr, &filename, &extension);
name = filename + extension;
}
}
int disc_number = GetDiscNumber() + 1;
std::string lower_name = name;
std::transform(lower_name.begin(), lower_name.end(), lower_name.begin(), ::tolower);
if (disc_number > 1 &&
lower_name.find(std::string("disc ") + std::to_string(disc_number)) == std::string::npos &&
lower_name.find(std::string("disc") + std::to_string(disc_number)) == std::string::npos)
{
info.push_back("Disc " + std::to_string(disc_number));
}
if (info.empty())
return QString::fromStdString(name);
std::ostringstream ss;
std::copy(info.begin(), info.end() - 1, std::ostream_iterator<std::string>(ss, ", "));
ss << info.back();
return QString::fromStdString(name + " (" + ss.str() + ")");
}
bool GameFile::IsInstalled() const
{
_assert_(m_platform == DiscIO::Platform::WII_WAD);

View File

@ -41,6 +41,7 @@ public:
u64 GetTitleID() const { return m_title_id; }
u16 GetRevision() const { return m_revision; }
QString GetInternalName() const { return m_internal_name; }
QString GetUniqueID() const;
u8 GetDiscNumber() const { return m_disc_number; }
u64 GetRawSize() const { return m_raw_size; }
QPixmap GetBanner() const { return m_banner; }

View File

@ -34,7 +34,7 @@ static bool CompressCB(const std::string&, float, void*);
GameList::GameList(QWidget* parent) : QStackedWidget(parent)
{
m_model = new GameListModel(this);
m_model = Settings::Instance().GetGameListModel();
m_list_proxy = new ListProxyModel(this);
m_list_proxy->setSortCaseSensitivity(Qt::CaseInsensitive);
m_list_proxy->setSortRole(Qt::InitialSortOrderRole);
@ -197,6 +197,20 @@ void GameList::ShowContextMenu(const QPoint&)
menu->addAction(tr("Open &containing folder"), this, &GameList::OpenContainingFolder);
menu->addAction(tr("Delete File..."), this, &GameList::DeleteFile);
QAction* netplay_host = new QAction(tr("Host with NetPlay"), menu);
connect(netplay_host, &QAction::triggered,
[this, game] { emit NetPlayHost(GameFile(game).GetUniqueID()); });
connect(this, &GameList::EmulationStarted, netplay_host,
[netplay_host] { netplay_host->setEnabled(false); });
connect(this, &GameList::EmulationStopped, netplay_host,
[netplay_host] { netplay_host->setEnabled(true); });
netplay_host->setEnabled(!Core::IsRunning());
menu->addAction(netplay_host);
menu->exec(QCursor::pos());
}

View File

@ -31,6 +31,7 @@ signals:
void GameSelected();
void EmulationStarted();
void EmulationStopped();
void NetPlayHost(const QString& game_id);
private:
void ShowContextMenu(const QPoint&);

View File

@ -27,6 +27,8 @@ public:
// Path of the Game at the specified index.
QString GetPath(int index) const { return m_games[index]->GetFilePath(); }
// Unique ID of the Game at the specified index
QString GetUniqueID(int index) const { return m_games[index]->GetUniqueID(); }
bool ShouldDisplayGameListItem(int index) const;
enum
{

View File

@ -18,6 +18,7 @@
#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/GCKeyboard.h"
@ -27,12 +28,13 @@
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "Core/HotkeyManager.h"
#include "Core/Movie.h"
#include "Core/NetPlayClient.h"
#include "Core/NetPlayProto.h"
#include "Core/NetPlayServer.h"
#include "Core/State.h"
#include "DolphinQt2/AboutDialog.h"
#include "DolphinQt2/Config/ControllersWindow.h"
#include "DolphinQt2/Config/Graphics/GraphicsWindow.h"
#include "DolphinQt2/Config/LoggerWidget.h"
#include "DolphinQt2/Config/Mapping/MappingWindow.h"
@ -40,6 +42,8 @@
#include "DolphinQt2/Host.h"
#include "DolphinQt2/HotkeyScheduler.h"
#include "DolphinQt2/MainWindow.h"
#include "DolphinQt2/NetPlay/NetPlayDialog.h"
#include "DolphinQt2/NetPlay/NetPlaySetupDialog.h"
#include "DolphinQt2/QtUtils/WindowActivationEventFilter.h"
#include "DolphinQt2/Resources.h"
#include "DolphinQt2/Settings.h"
@ -71,6 +75,8 @@ MainWindow::MainWindow() : QMainWindow(nullptr)
InitControllers();
InitCoreCallbacks();
NetPlayInit();
}
MainWindow::~MainWindow()
@ -195,6 +201,7 @@ void MainWindow::ConnectMenuBar()
// Tools
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);
// View
connect(m_menu_bar, &MenuBar::ShowList, m_game_list, &GameList::SetListView);
@ -258,6 +265,7 @@ void MainWindow::ConnectToolBar()
void MainWindow::ConnectGameList()
{
connect(m_game_list, &GameList::GameSelected, this, &MainWindow::Play);
connect(m_game_list, &GameList::NetPlayHost, this, &MainWindow::NetPlayHost);
connect(this, &MainWindow::EmulationStarted, m_game_list, &GameList::EmulationStarted);
connect(this, &MainWindow::EmulationStopped, m_game_list, &GameList::EmulationStopped);
}
@ -355,8 +363,9 @@ bool MainWindow::RequestStop()
if (SConfig::GetInstance().bConfirmStop)
{
const Core::State state = Core::GetState();
// TODO: Set to false when Netplay is running as a CPU thread
bool pause = true;
// Only pause the game, if NetPlay is not running
bool pause = Settings::Instance().GetNetPlayClient() != nullptr;
if (pause)
Core::SetState(Core::State::Paused);
@ -546,6 +555,13 @@ void MainWindow::ShowGraphicsWindow()
m_graphics_window->activateWindow();
}
void MainWindow::ShowNetPlaySetupDialog()
{
m_netplay_setup_dialog->show();
m_netplay_setup_dialog->raise();
m_netplay_setup_dialog->activateWindow();
}
void MainWindow::StateLoad()
{
QString path = QFileDialog::getOpenFileName(this, tr("Select a File"), QDir::currentPath(),
@ -616,6 +632,143 @@ void MainWindow::BootWiiSystemMenu()
Common::GetTitleContentPath(Titles::SYSTEM_MENU, Common::FROM_CONFIGURED_ROOT)));
}
void MainWindow::NetPlayInit()
{
m_netplay_setup_dialog = new NetPlaySetupDialog(this);
m_netplay_dialog = new NetPlayDialog(this);
connect(m_netplay_dialog, &NetPlayDialog::Boot, this, &MainWindow::StartGame);
connect(m_netplay_dialog, &NetPlayDialog::Stop, this, &MainWindow::RequestStop);
connect(m_netplay_dialog, &NetPlayDialog::rejected, this, &MainWindow::NetPlayQuit);
connect(this, &MainWindow::EmulationStopped, m_netplay_dialog, &NetPlayDialog::EmulationStopped);
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
std::string host_ip, traversal_host, nickname;
int host_port, traversal_port;
bool is_traversal;
if (Settings::Instance().GetNetPlayServer() != nullptr)
{
host_ip = "127.0.0.1";
host_port = Settings::Instance().GetNetPlayServer()->GetPort();
}
else
{
host_ip = Config::Get(Config::NETPLAY_HOST_CODE);
host_port = Config::Get(Config::NETPLAY_HOST_PORT);
}
std::string traversal_choice = Config::Get(Config::NETPLAY_TRAVERSAL_CHOICE);
is_traversal = traversal_choice == "traversal";
traversal_host = Config::Get(Config::NETPLAY_TRAVERSAL_SERVER);
traversal_port = Config::Get(Config::NETPLAY_TRAVERSAL_PORT);
nickname = Config::Get(Config::NETPLAY_NICKNAME);
// Create Client
Settings::Instance().ResetNetPlayClient(
new NetPlayClient(host_ip, host_port, m_netplay_dialog, nickname,
Settings::Instance().GetNetPlayServer() != nullptr ? false : is_traversal,
traversal_host, traversal_port));
if (!Settings::Instance().GetNetPlayClient()->IsConnected())
{
QMessageBox::critical(nullptr, QObject::tr("Error"),
QObject::tr("Failed to connect to server"));
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
std::string traversal_host, nickname;
int host_port, traversal_port;
bool is_traversal, use_upnp;
host_port = Config::Get(Config::NETPLAY_HOST_PORT);
std::string traversal_choice;
traversal_choice = Config::Get(Config::NETPLAY_TRAVERSAL_CHOICE);
is_traversal = traversal_choice == "traversal";
use_upnp = Config::Get(Config::NETPLAY_USE_UPNP);
traversal_host = Config::Get(Config::NETPLAY_TRAVERSAL_SERVER);
traversal_port = Config::Get(Config::NETPLAY_TRAVERSAL_PORT);
nickname = Config::Get(Config::NETPLAY_NICKNAME);
if (is_traversal)
host_port = Config::Get(Config::NETPLAY_LISTEN_PORT);
// Create Server
Settings::Instance().ResetNetPlayServer(
new NetPlayServer(host_port, 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));
return false;
}
Settings::Instance().GetNetPlayServer()->ChangeGame(game_id.toStdString());
#ifdef USE_UPNP
if (use_upnp)
Settings::Instance().GetNetPlayServer()->TryPortmapping(host_port);
#endif
// Join our local server
return NetPlayJoin();
}
void MainWindow::NetPlayQuit()
{
Settings::Instance().ResetNetPlayClient();
Settings::Instance().ResetNetPlayServer();
}
bool MainWindow::eventFilter(QObject* object, QEvent* event)
{
if (event->type() == QEvent::Close)

View File

@ -17,6 +17,10 @@
class HotkeyScheduler;
class LoggerWidget;
class MappingWindow;
class NetPlayClient;
class NetPlayDialog;
class NetPlayServer;
class NetPlaySetupDialog;
class SettingsWindow;
class ControllersWindow;
class DragEnterEvent;
@ -88,6 +92,12 @@ private:
void ShowGraphicsWindow();
void ShowAboutDialog();
void ShowHotkeyDialog();
void ShowNetPlaySetupDialog();
void NetPlayInit();
bool NetPlayJoin();
bool NetPlayHost(const QString& game_id);
void NetPlayQuit();
void OnStopComplete();
void dragEnterEvent(QDragEnterEvent* event) override;
@ -109,6 +119,8 @@ private:
ControllersWindow* m_controllers_window;
SettingsWindow* m_settings_window;
MappingWindow* m_hotkey_window;
NetPlayDialog* m_netplay_dialog;
NetPlaySetupDialog* m_netplay_setup_dialog;
GraphicsWindow* m_graphics_window;
LoggerWidget* m_logger_widget;
};

View File

@ -88,6 +88,9 @@ void MenuBar::AddToolsMenu()
QMenu* tools_menu = addMenu(tr("&Tools"));
m_wad_install_action = tools_menu->addAction(tr("Install WAD..."), this, &MenuBar::InstallWAD);
tools_menu->addAction(tr("Start NetPlay..."), this, &MenuBar::StartNetPlay);
tools_menu->addSeparator();
// Label will be set by a NANDRefresh later
m_boot_sysmenu = tools_menu->addAction(QStringLiteral(""), [this] { emit BootWiiSystemMenu(); });
m_boot_sysmenu->setEnabled(false);

View File

@ -38,6 +38,7 @@ signals:
void Fullscreen();
void FrameAdvance();
void Screenshot();
void StartNetPlay();
void StateLoad();
void StateSave();
void StateLoadSlot();

View File

@ -0,0 +1,70 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "DolphinQt2/NetPlay/GameListDialog.h"
#include <QDialogButtonBox>
#include <QListWidget>
#include <QVBoxLayout>
#include "DolphinQt2/GameList/GameListModel.h"
#include "DolphinQt2/Settings.h"
GameListDialog::GameListDialog(QWidget* parent) : QDialog(parent)
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
CreateWidgets();
ConnectWidgets();
}
void GameListDialog::CreateWidgets()
{
m_main_layout = new QVBoxLayout;
m_game_list = new QListWidget;
m_button_box = new QDialogButtonBox(QDialogButtonBox::Ok);
m_button_box->setEnabled(false);
m_main_layout->addWidget(m_game_list);
m_main_layout->addWidget(m_button_box);
setLayout(m_main_layout);
}
void GameListDialog::ConnectWidgets()
{
connect(m_game_list, &QListWidget::itemSelectionChanged, [this] {
int row = m_game_list->currentRow();
m_button_box->setEnabled(row != -1);
m_game_id = m_game_list->currentItem()->text();
});
connect(m_button_box, &QDialogButtonBox::accepted, this, &GameListDialog::accept);
}
void GameListDialog::PopulateGameList()
{
auto* game_list_model = Settings::Instance().GetGameListModel();
m_game_list->clear();
for (int i = 0; i < game_list_model->rowCount(QModelIndex()); i++)
{
auto* item = new QListWidgetItem(game_list_model->GetUniqueID(i));
m_game_list->addItem(item);
}
m_game_list->sortItems();
}
const QString& GameListDialog::GetSelectedUniqueID()
{
return m_game_id;
}
int GameListDialog::exec()
{
PopulateGameList();
return QDialog::exec();
}

View File

@ -0,0 +1,32 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <QDialog>
class GameListModel;
class QVBoxLayout;
class QListWidget;
class QDialogButtonBox;
class GameListDialog : public QDialog
{
Q_OBJECT
public:
explicit GameListDialog(QWidget* parent);
int exec();
const QString& GetSelectedUniqueID();
private:
void CreateWidgets();
void ConnectWidgets();
void PopulateGameList();
QVBoxLayout* m_main_layout;
QListWidget* m_game_list;
QDialogButtonBox* m_button_box;
QString m_game_id;
};

View File

@ -0,0 +1,134 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "MD5Dialog.h"
#include "DolphinQt2/Settings.h"
#include <QDialogButtonBox>
#include <QGroupBox>
#include <QLabel>
#include <QProgressBar>
#include <QVBoxLayout>
static QString GetPlayerNameFromPID(int pid)
{
QString player_name = QObject::tr("Invalid Player ID");
for (const auto* player : Settings::Instance().GetNetPlayClient()->GetPlayers())
{
if (player->pid == pid)
{
player_name = QString::fromStdString(player->name);
break;
}
}
return player_name;
}
MD5Dialog::MD5Dialog(QWidget* parent) : QDialog(parent)
{
CreateWidgets();
ConnectWidgets();
setWindowTitle(tr("MD5 Checksum"));
}
void MD5Dialog::CreateWidgets()
{
m_main_layout = new QVBoxLayout;
m_progress_box = new QGroupBox;
m_progress_layout = new QVBoxLayout;
m_button_box = new QDialogButtonBox(QDialogButtonBox::Close);
m_check_label = new QLabel;
m_progress_box->setLayout(m_progress_layout);
m_main_layout->addWidget(m_progress_box);
m_main_layout->addWidget(m_check_label);
m_main_layout->addWidget(m_button_box);
setLayout(m_main_layout);
}
void MD5Dialog::ConnectWidgets()
{
connect(m_button_box, &QDialogButtonBox::rejected, this, &MD5Dialog::reject);
}
void MD5Dialog::show(const QString& title)
{
m_progress_box->setTitle(title);
for (auto& pair : m_progress_bars)
{
m_progress_layout->removeWidget(pair.second);
pair.second->deleteLater();
}
for (auto& pair : m_status_labels)
{
m_progress_layout->removeWidget(pair.second);
pair.second->deleteLater();
}
m_progress_bars.clear();
m_status_labels.clear();
for (const auto* player : Settings::Instance().GetNetPlayClient()->GetPlayers())
{
m_progress_bars[player->pid] = new QProgressBar;
m_status_labels[player->pid] = new QLabel;
m_progress_layout->addWidget(m_progress_bars[player->pid]);
m_progress_layout->addWidget(m_status_labels[player->pid]);
}
m_last_result = "";
QDialog::show();
}
void MD5Dialog::SetProgress(int pid, int progress)
{
QString player_name = GetPlayerNameFromPID(pid);
if (!m_status_labels.count(pid))
return;
m_status_labels[pid]->setText(
tr("%1[%2]: %3 %").arg(player_name, QString::number(pid), QString::number(progress)));
m_progress_bars[pid]->setValue(progress);
}
void MD5Dialog::SetResult(int pid, const std::string& result)
{
QString player_name = GetPlayerNameFromPID(pid);
if (!m_status_labels.count(pid))
return;
m_status_labels[pid]->setText(
tr("%1[%2]: %3").arg(player_name, QString::number(pid), QString::fromStdString(result)));
if (m_last_result == "")
{
m_check_label->setText(tr("The hashes match!"));
return;
}
if (m_last_result != result)
{
m_check_label->setText(tr("The hashes do not match!"));
}
m_last_result = result;
}
void MD5Dialog::reject()
{
auto* server = Settings::Instance().GetNetPlayServer();
if (server)
server->AbortMD5();
QDialog::reject();
}

View File

@ -0,0 +1,42 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <QDialog>
class QDialogButtonBox;
class QGroupBox;
class QLabel;
class QProgressBar;
class QVBoxLayout;
class QWidget;
class MD5Dialog : public QDialog
{
Q_OBJECT
public:
MD5Dialog(QWidget* parent);
void show(const QString& title);
void SetProgress(int pid, int progress);
void SetResult(int pid, const std::string& md5);
void reject() override;
private:
void CreateWidgets();
void ConnectWidgets();
std::map<int, QProgressBar*> m_progress_bars;
std::map<int, QLabel*> m_status_labels;
std::string m_last_result;
QGroupBox* m_progress_box;
QVBoxLayout* m_progress_layout;
QVBoxLayout* m_main_layout;
QLabel* m_check_label;
QDialogButtonBox* m_button_box;
};

View File

@ -0,0 +1,575 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "DolphinQt2/NetPlay/NetPlayDialog.h"
#include <QApplication>
#include <QCheckBox>
#include <QClipboard>
#include <QComboBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QListWidget>
#include <QMessageBox>
#include <QProgressDialog>
#include <QPushButton>
#include <QSpinBox>
#include <QTextEdit>
#include <sstream>
#include "Common/CommonPaths.h"
#include "Common/TraversalClient.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/NetPlayServer.h"
#include "DolphinQt2/GameList/GameList.h"
#include "DolphinQt2/NetPlay/GameListDialog.h"
#include "DolphinQt2/NetPlay/MD5Dialog.h"
#include "DolphinQt2/NetPlay/PadMappingDialog.h"
#include "DolphinQt2/QtUtils/QueueOnObject.h"
#include "DolphinQt2/QtUtils/RunOnObject.h"
#include "DolphinQt2/Settings.h"
#include "VideoCommon/VideoConfig.h"
NetPlayDialog::NetPlayDialog(QWidget* parent)
: QDialog(parent), m_game_list_model(Settings::Instance().GetGameListModel())
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setWindowTitle(tr("Dolphin NetPlay"));
m_pad_mapping = new PadMappingDialog(this);
m_md5_dialog = new MD5Dialog(this);
CreateChatLayout();
CreatePlayersLayout();
CreateMainLayout();
ConnectWidgets();
}
void NetPlayDialog::CreateMainLayout()
{
m_main_layout = new QGridLayout;
m_game_button = new QPushButton;
m_md5_box = new QComboBox;
m_start_button = new QPushButton(tr("Start"));
m_buffer_size_box = new QSpinBox;
m_save_sd_box = new QCheckBox(tr("Write save / SD-Card data"));
m_load_wii_box = new QCheckBox(tr("Load Wii Save"));
m_record_input_box = new QCheckBox(tr("Record inputs"));
m_buffer_label = new QLabel(tr("Buffer:"));
m_quit_button = new QPushButton(tr("Quit"));
m_game_button->setDefault(false);
m_game_button->setAutoDefault(false);
for (const QString& text :
{tr("MD5 Check:"), tr("Current game"), tr("Other game"), tr("SD card")})
m_md5_box->addItem(text);
m_main_layout->addWidget(m_game_button, 0, 0);
m_main_layout->addWidget(m_md5_box, 0, 1);
m_main_layout->addWidget(m_chat_box, 1, 0);
m_main_layout->addWidget(m_players_box, 1, 1);
auto* options_widget = new QHBoxLayout;
options_widget->addWidget(m_start_button);
options_widget->addWidget(m_buffer_label);
options_widget->addWidget(m_buffer_size_box);
options_widget->addWidget(m_save_sd_box);
options_widget->addWidget(m_load_wii_box);
options_widget->addWidget(m_record_input_box);
options_widget->addWidget(m_quit_button);
m_main_layout->addLayout(options_widget, 2, 0, 1, -1, Qt::AlignRight);
setLayout(m_main_layout);
}
void NetPlayDialog::CreateChatLayout()
{
m_chat_box = new QGroupBox(tr("Chat"));
m_chat_edit = new QTextEdit;
m_chat_type_edit = new QLineEdit;
m_chat_send_button = new QPushButton(tr("Send"));
m_chat_send_button->setDefault(false);
m_chat_send_button->setAutoDefault(false);
m_chat_edit->setReadOnly(true);
auto* layout = new QGridLayout;
layout->addWidget(m_chat_edit, 0, 0, 1, -1);
layout->addWidget(m_chat_type_edit, 1, 0);
layout->addWidget(m_chat_send_button, 1, 1);
m_chat_box->setLayout(layout);
}
void NetPlayDialog::CreatePlayersLayout()
{
m_players_box = new QGroupBox(tr("Players"));
m_room_box = new QComboBox;
m_hostcode_label = new QLabel;
m_hostcode_action_button = new QPushButton(tr("Copy"));
m_players_list = new QListWidget;
m_kick_button = new QPushButton(tr("Kick Player"));
m_assign_ports_button = new QPushButton(tr("Assign Controller Ports"));
auto* layout = new QGridLayout;
layout->addWidget(m_room_box, 0, 0);
layout->addWidget(m_hostcode_label, 0, 1);
layout->addWidget(m_hostcode_action_button, 0, 2);
layout->addWidget(m_players_list, 1, 0, 1, -1);
layout->addWidget(m_kick_button, 2, 0, 1, -1);
layout->addWidget(m_assign_ports_button, 3, 0, 1, -1);
m_players_box->setLayout(layout);
}
void NetPlayDialog::ConnectWidgets()
{
// Players
connect(m_room_box, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
&NetPlayDialog::UpdateGUI);
connect(m_hostcode_action_button, &QPushButton::clicked, [this] {
if (m_is_copy_button_retry && m_room_box->currentIndex() == 0)
g_TraversalClient->ReconnectToServer();
else
QApplication::clipboard()->setText(m_hostcode_label->text());
});
connect(m_players_list, &QListWidget::itemSelectionChanged, [this] {
int row = m_players_list->currentRow();
m_kick_button->setEnabled(row > 0 &&
!m_players_list->currentItem()->data(Qt::UserRole).isNull());
});
connect(m_kick_button, &QPushButton::clicked, [this] {
auto id = m_players_list->currentItem()->data(Qt::UserRole).toInt();
Settings::Instance().GetNetPlayServer()->KickPlayer(id);
});
connect(m_assign_ports_button, &QPushButton::clicked, [this] {
m_pad_mapping->exec();
Settings::Instance().GetNetPlayServer()->SetPadMapping(m_pad_mapping->GetGCPadArray());
Settings::Instance().GetNetPlayServer()->SetWiimoteMapping(m_pad_mapping->GetWiimoteArray());
});
// Chat
connect(m_chat_send_button, &QPushButton::clicked, this, &NetPlayDialog::OnChat);
connect(m_chat_type_edit, &QLineEdit::returnPressed, this, &NetPlayDialog::OnChat);
// Other
connect(m_buffer_size_box, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
[this](int value) {
if (Settings::Instance().GetNetPlayServer() != nullptr)
Settings::Instance().GetNetPlayServer()->AdjustPadBufferSize(value);
});
connect(m_start_button, &QPushButton::clicked, this, &NetPlayDialog::OnStart);
connect(m_quit_button, &QPushButton::clicked, this, &NetPlayDialog::reject);
connect(m_md5_box, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
&NetPlayDialog::OnMD5Combo);
connect(m_game_button, &QPushButton::clicked, [this] {
GameListDialog gld(this);
if (gld.exec() == QDialog::Accepted)
{
auto unique_id = gld.GetSelectedUniqueID();
Settings::Instance().GetNetPlayServer()->ChangeGame(unique_id.toStdString());
}
});
connect(this, &NetPlayDialog::EmulationStopped, this, [this] {
if (isVisible())
GameStatusChanged(false);
});
}
void NetPlayDialog::OnChat()
{
QueueOnObject(this, [this] {
auto msg = m_chat_type_edit->text().toStdString();
Settings::Instance().GetNetPlayClient()->SendChatMessage(msg);
m_chat_type_edit->clear();
DisplayMessage(QStringLiteral("%1: %2").arg(QString::fromStdString(m_nickname),
QString::fromStdString(msg)),
"blue");
});
}
void NetPlayDialog::OnStart()
{
if (!Settings::Instance().GetNetPlayClient()->DoAllPlayersHaveGame())
{
if (QMessageBox::question(this, tr("Warning"),
tr("Not all players have the game. Do you really want to start?")) ==
QMessageBox::No)
return;
}
NetSettings settings;
// Copy all relevant settings
SConfig& instance = SConfig::GetInstance();
settings.m_CPUthread = instance.bCPUThread;
settings.m_CPUcore = instance.iCPUCore;
settings.m_EnableCheats = instance.bEnableCheats;
settings.m_SelectedLanguage = instance.SelectedLanguage;
settings.m_OverrideGCLanguage = instance.bOverrideGCLanguage;
settings.m_ProgressiveScan = instance.bProgressive;
settings.m_PAL60 = instance.bPAL60;
settings.m_DSPHLE = instance.bDSPHLE;
settings.m_DSPEnableJIT = instance.m_DSPEnableJIT;
settings.m_WriteToMemcard = m_save_sd_box->isChecked();
settings.m_CopyWiiSave = m_load_wii_box->isChecked();
settings.m_OCEnable = instance.m_OCEnable;
settings.m_OCFactor = instance.m_OCFactor;
settings.m_EXIDevice[0] = instance.m_EXIDevice[0];
settings.m_EXIDevice[1] = instance.m_EXIDevice[1];
Settings::Instance().GetNetPlayServer()->SetNetSettings(settings);
Settings::Instance().GetNetPlayServer()->StartGame();
}
void NetPlayDialog::OnMD5Combo(int index)
{
std::string identifier;
switch (index)
{
case 0:
return;
case 1: // Current game
identifier = m_current_game;
break;
case 2: // Other game
{
GameListDialog gld(this);
if (gld.exec() == QDialog::Accepted)
{
identifier = gld.GetSelectedUniqueID().toStdString();
break;
}
else
{
m_md5_box->setCurrentIndex(0);
return;
}
}
case 3: // SD Card
identifier = WII_SDCARD;
break;
}
Settings::Instance().GetNetPlayServer()->ComputeMD5(identifier);
}
void NetPlayDialog::reject()
{
if (QMessageBox::question(this, tr("Confirmation"),
tr("Are you sure you want to quit NetPlay?")) == QMessageBox::Yes)
{
QDialog::reject();
}
}
void NetPlayDialog::show(std::string nickname, bool use_traversal)
{
m_nickname = nickname;
m_use_traversal = use_traversal;
m_room_box->clear();
m_chat_edit->clear();
m_chat_type_edit->clear();
bool is_hosting = Settings::Instance().GetNetPlayServer() != nullptr;
if (is_hosting)
{
if (use_traversal)
m_room_box->addItem(tr("Room ID"));
for (const auto& iface : Settings::Instance().GetNetPlayServer()->GetInterfaceSet())
m_room_box->addItem(QString::fromStdString(iface));
}
m_start_button->setHidden(!is_hosting);
m_save_sd_box->setHidden(!is_hosting);
m_load_wii_box->setHidden(!is_hosting);
m_buffer_size_box->setHidden(!is_hosting);
m_buffer_label->setHidden(!is_hosting);
m_kick_button->setHidden(!is_hosting);
m_assign_ports_button->setHidden(!is_hosting);
m_md5_box->setHidden(!is_hosting);
m_room_box->setHidden(!is_hosting);
m_hostcode_label->setHidden(!is_hosting);
m_hostcode_action_button->setHidden(!is_hosting);
m_game_button->setEnabled(is_hosting);
m_kick_button->setEnabled(false);
QDialog::show();
UpdateGUI();
}
void NetPlayDialog::UpdateGUI()
{
// Update player list
std::vector<int> player_ids;
std::string tmp;
Settings::Instance().GetNetPlayClient()->GetPlayerList(tmp, player_ids);
std::istringstream ss(tmp);
int row = m_players_list->currentRow();
unsigned int i = 0;
m_players_list->clear();
while (std::getline(ss, tmp))
{
auto text = QString::fromStdString(tmp);
if (!text.isEmpty())
{
QListWidgetItem* item = new QListWidgetItem(text);
if (player_ids.size() > i && !text.startsWith(QStringLiteral("Ping:")) &&
!text.startsWith(QStringLiteral("Status:")))
{
item->setData(Qt::UserRole, player_ids[i]);
i++;
}
m_players_list->addItem(item);
}
}
if (row != -1)
m_players_list->setCurrentRow(row, QItemSelectionModel::SelectCurrent);
// Update Room ID / IP label
if (m_use_traversal && m_room_box->currentIndex() == 0)
{
switch (g_TraversalClient->m_State)
{
case TraversalClient::Connecting:
m_hostcode_label->setText(tr("..."));
m_hostcode_action_button->setEnabled(false);
break;
case TraversalClient::Connected:
m_hostcode_label->setText(QString::fromStdString(
std::string(g_TraversalClient->m_HostId.data(), g_TraversalClient->m_HostId.size())));
m_hostcode_action_button->setEnabled(true);
m_hostcode_action_button->setText(tr("Copy"));
m_is_copy_button_retry = false;
break;
case TraversalClient::Failure:
m_hostcode_label->setText(tr("Error"));
m_hostcode_action_button->setText(tr("Retry"));
m_hostcode_action_button->setEnabled(true);
m_is_copy_button_retry = true;
break;
}
}
else if (Settings::Instance().GetNetPlayServer())
{
m_hostcode_label->setText(
QString::fromStdString(Settings::Instance().GetNetPlayServer()->GetInterfaceHost(
m_room_box->currentText().toStdString())));
m_hostcode_action_button->setText(tr("Copy"));
m_hostcode_action_button->setEnabled(true);
}
}
// NetPlayUI methods
void NetPlayDialog::BootGame(const std::string& filename)
{
emit Boot(QString::fromStdString(filename));
}
void NetPlayDialog::StopGame()
{
emit Stop();
}
void NetPlayDialog::Update()
{
QueueOnObject(this, [this] { UpdateGUI(); });
}
void NetPlayDialog::DisplayMessage(const QString& msg, const std::string& color, int duration)
{
QueueOnObject(m_chat_edit, [this, color, msg] {
m_chat_edit->append(
QStringLiteral("<font color='%1'>%2</font>").arg(QString::fromStdString(color), msg));
});
if (g_ActiveConfig.bShowNetPlayMessages && Core::IsRunning())
{
u32 osd_color;
// Convert the color string to a OSD color
if (color == "red")
osd_color = OSD::Color::RED;
else if (color == "cyan")
osd_color = OSD::Color::CYAN;
else if (color == "green")
osd_color = OSD::Color::GREEN;
else
osd_color = OSD::Color::YELLOW;
OSD::AddTypedMessage(OSD::MessageType::NetPlayBuffer, msg.toStdString(), OSD::Duration::NORMAL,
osd_color);
}
}
void NetPlayDialog::AppendChat(const std::string& msg)
{
DisplayMessage(QString::fromStdString(msg), "");
}
void NetPlayDialog::OnMsgChangeGame(const std::string& title)
{
QString qtitle = QString::fromStdString(title);
QueueOnObject(this, [this, qtitle, title] {
m_game_button->setText(qtitle);
m_current_game = title;
});
DisplayMessage(tr("Game changed to \"%1\"").arg(qtitle), "pink");
}
void NetPlayDialog::GameStatusChanged(bool running)
{
QueueOnObject(this, [this, running] {
if (Settings::Instance().GetNetPlayServer() != nullptr)
{
m_start_button->setEnabled(!running);
m_game_button->setEnabled(!running);
m_load_wii_box->setEnabled(!running);
m_save_sd_box->setEnabled(!running);
m_assign_ports_button->setEnabled(!running);
}
m_record_input_box->setEnabled(!running);
});
}
void NetPlayDialog::OnMsgStartGame()
{
DisplayMessage(tr("Started game"), "green");
GameStatusChanged(true);
QueueOnObject(this, [this] {
Settings::Instance().GetNetPlayClient()->StartGame(FindGame(m_current_game));
});
}
void NetPlayDialog::OnMsgStopGame()
{
DisplayMessage(tr("Stopped game"), "red");
GameStatusChanged(false);
}
void NetPlayDialog::OnPadBufferChanged(u32 buffer)
{
QueueOnObject(this, [this, buffer] { m_buffer_size_box->setValue(buffer); });
DisplayMessage(tr("Pad size changed to %1").arg(buffer), "gray");
}
void NetPlayDialog::OnDesync(u32 frame, const std::string& player)
{
DisplayMessage(tr("Possible desync detected: %1 might have desynced at frame %2")
.arg(QString::fromStdString(player), QString::number(frame)),
"red", OSD::Duration::VERY_LONG);
}
void NetPlayDialog::OnConnectionLost()
{
DisplayMessage(tr("Lost connection to NetPlay server..."), "red");
}
void NetPlayDialog::OnTraversalError(int error)
{
QueueOnObject(this, [this, error] {
switch (error)
{
case TraversalClient::BadHost:
QMessageBox::critical(this, tr("Traversal Error"), tr("Couldn't look up central server"));
QDialog::reject();
break;
case TraversalClient::VersionTooOld:
QMessageBox::critical(this, tr("Traversal Error"),
tr("Dolphin is too old for traversal server"));
QDialog::reject();
break;
case TraversalClient::ServerForgotAboutUs:
case TraversalClient::SocketSendError:
case TraversalClient::ResendTimeout:
UpdateGUI();
break;
}
});
}
bool NetPlayDialog::IsRecording()
{
return RunOnObject(this, [this] { return m_record_input_box->isChecked(); });
}
std::string NetPlayDialog::FindGame(const std::string& game)
{
return RunOnObject(this, [this, game] {
for (int i = 0; i < m_game_list_model->rowCount(QModelIndex()); i++)
{
if (m_game_list_model->GetUniqueID(i).toStdString() == game)
return m_game_list_model->GetPath(i).toStdString();
}
return std::string("");
});
}
void NetPlayDialog::ShowMD5Dialog(const std::string& file_identifier)
{
QueueOnObject(this, [this, file_identifier] {
m_md5_box->setEnabled(false);
m_md5_box->setCurrentIndex(0);
if (m_md5_dialog->isVisible())
m_md5_dialog->close();
m_md5_dialog->show(QString::fromStdString(file_identifier));
});
}
void NetPlayDialog::SetMD5Progress(int pid, int progress)
{
QueueOnObject(this, [this, pid, progress] {
if (m_md5_dialog->isVisible())
m_md5_dialog->SetProgress(pid, progress);
});
}
void NetPlayDialog::SetMD5Result(int pid, const std::string& result)
{
QueueOnObject(this, [this, pid, result] {
m_md5_dialog->SetResult(pid, result);
m_md5_box->setEnabled(true);
});
}
void NetPlayDialog::AbortMD5()
{
QueueOnObject(this, [this] {
m_md5_dialog->close();
m_md5_box->setEnabled(true);
});
}

View File

@ -0,0 +1,110 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <QDialog>
#include "Core/NetPlayClient.h"
#include "VideoCommon/OnScreenDisplay.h"
class MD5Dialog;
class GameListModel;
class NetPlayServer;
class PadMappingDialog;
class QCheckBox;
class QComboBox;
class QGridLayout;
class QGroupBox;
class QLabel;
class QLineEdit;
class QListWidget;
class QPushButton;
class QSpinBox;
class QTextEdit;
class NetPlayDialog : public QDialog, public NetPlayUI
{
Q_OBJECT
public:
NetPlayDialog(QWidget* parent);
void show(std::string nickname, bool use_traversal);
void reject() override;
// NetPlayUI methods
void BootGame(const std::string& filename) override;
void StopGame() override;
void Update() override;
void AppendChat(const std::string& msg) override;
void OnMsgChangeGame(const std::string& filename) override;
void OnMsgStartGame() override;
void OnMsgStopGame() override;
void OnPadBufferChanged(u32 buffer) override;
void OnDesync(u32 frame, const std::string& player) override;
void OnConnectionLost() override;
void OnTraversalError(int error) override;
bool IsRecording() override;
std::string FindGame(const std::string& game) override;
void ShowMD5Dialog(const std::string& file_identifier) override;
void SetMD5Progress(int pid, int progress) override;
void SetMD5Result(int pid, const std::string& result) override;
void AbortMD5() override;
signals:
void EmulationStopped();
void Boot(const QString& filename);
void Stop();
private:
void CreateChatLayout();
void CreatePlayersLayout();
void CreateMainLayout();
void ConnectWidgets();
void OnChat();
void OnStart();
void OnMD5Combo(int index);
void DisplayMessage(const QString& msg, const std::string& color,
int duration = OSD::Duration::NORMAL);
void UpdateGUI();
void GameStatusChanged(bool running);
void SetGame(const QString& game_path);
// Chat
QGroupBox* m_chat_box;
QTextEdit* m_chat_edit;
QLineEdit* m_chat_type_edit;
QPushButton* m_chat_send_button;
// Players
QGroupBox* m_players_box;
QComboBox* m_room_box;
QLabel* m_hostcode_label;
QPushButton* m_hostcode_action_button;
QListWidget* m_players_list;
QPushButton* m_kick_button;
QPushButton* m_assign_ports_button;
// Other
QPushButton* m_game_button;
QComboBox* m_md5_box;
QPushButton* m_start_button;
QLabel* m_buffer_label;
QSpinBox* m_buffer_size_box;
QCheckBox* m_save_sd_box;
QCheckBox* m_load_wii_box;
QCheckBox* m_record_input_box;
QPushButton* m_quit_button;
QGridLayout* m_main_layout;
MD5Dialog* m_md5_dialog;
PadMappingDialog* m_pad_mapping;
std::string m_current_game;
std::string m_nickname;
GameListModel* m_game_list_model = nullptr;
bool m_use_traversal = false;
bool m_is_copy_button_retry = false;
};

View File

@ -0,0 +1,267 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "DolphinQt2/NetPlay/NetPlaySetupDialog.h"
#include "Core/Config/NetplaySettings.h"
#include "DolphinQt2/GameList/GameListModel.h"
#include "DolphinQt2/Settings.h"
#include <QCheckBox>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QListWidget>
#include <QMessageBox>
#include <QPushButton>
#include <QSettings>
#include <QSpinBox>
#include <QTabWidget>
NetPlaySetupDialog::NetPlaySetupDialog(QWidget* parent)
: QDialog(parent), m_game_list_model(Settings::Instance().GetGameListModel())
{
setWindowTitle(tr("Dolphin NetPlay Setup"));
CreateMainLayout();
std::string nickname = Config::Get(Config::NETPLAY_NICKNAME);
std::string traversal_choice = Config::Get(Config::NETPLAY_TRAVERSAL_CHOICE);
int connect_port = Config::Get(Config::NETPLAY_CONNECT_PORT);
int host_port = Config::Get(Config::NETPLAY_HOST_PORT);
int host_listen_port = Config::Get(Config::NETPLAY_LISTEN_PORT);
#ifdef USE_UPNP
bool use_upnp = Config::Get(Config::NETPLAY_USE_UPNP);
m_host_upnp->setChecked(use_upnp);
#endif
m_nickname_edit->setText(QString::fromStdString(nickname));
m_connection_type->setCurrentIndex(traversal_choice == "direct" ? 0 : 1);
m_connect_port_box->setValue(connect_port);
m_host_port_box->setValue(host_port);
m_host_force_port_check->setChecked(false);
m_host_force_port_box->setValue(host_listen_port);
m_host_force_port_box->setEnabled(false);
OnConnectionTypeChanged(m_connection_type->currentIndex());
int selected_game = QSettings().value(QStringLiteral("netplay/hostgame"), 0).toInt();
if (selected_game >= m_host_games->count())
selected_game = 0;
m_host_games->setCurrentItem(m_host_games->item(selected_game));
ConnectWidgets();
}
void NetPlaySetupDialog::CreateMainLayout()
{
m_main_layout = new QGridLayout;
m_button_box = new QDialogButtonBox(QDialogButtonBox::Cancel);
m_nickname_edit = new QLineEdit;
m_connection_type = new QComboBox;
m_reset_traversal_button = new QPushButton(tr("Reset Traversal Settings"));
m_tab_widget = new QTabWidget;
// Connection widget
auto* connection_widget = new QWidget;
auto* connection_layout = new QGridLayout;
m_ip_label = new QLabel;
m_ip_edit = new QLineEdit;
m_connect_port_label = new QLabel(tr("Port:"));
m_connect_port_box = new QSpinBox;
m_connect_button = new QPushButton(tr("Connect"));
m_connect_port_box->setMaximum(65535);
connection_layout->addWidget(m_ip_label, 0, 0);
connection_layout->addWidget(m_ip_edit, 0, 1);
connection_layout->addWidget(m_connect_port_label, 0, 2);
connection_layout->addWidget(m_connect_port_box, 0, 3);
connection_layout->addWidget(
new QLabel(tr(
"ALERT:\n\n"
"All players must use the same Dolphin version.\n"
"All memory cards, SD cards and cheats must be identical between players or disabled.\n"
"If DSP LLE is used, DSP ROMs must be identical between players.\n"
"If connecting directly, the host must have the chosen UDP port open/forwarded!\n"
"\n"
"Wii Remote support in netplay is experimental and should not be expected to work.\n")),
1, 0, -1, -1);
connection_layout->addWidget(m_connect_button, 3, 3, Qt::AlignRight);
connection_widget->setLayout(connection_layout);
// Host widget
auto* host_widget = new QWidget;
auto* host_layout = new QGridLayout;
m_host_port_label = new QLabel(tr("Port:"));
m_host_port_box = new QSpinBox;
m_host_force_port_check = new QCheckBox(tr("Force Listen Port:"));
m_host_force_port_box = new QSpinBox;
#ifdef USE_UPNP
m_host_upnp = new QCheckBox(tr("Forward port (UPnP)"));
#endif
m_host_games = new QListWidget;
m_host_button = new QPushButton(tr("Host"));
m_host_port_box->setMaximum(65535);
m_host_force_port_box->setMaximum(65535);
host_layout->addWidget(m_host_port_label, 0, 0);
host_layout->addWidget(m_host_port_box, 0, 1);
#ifdef USE_UPNP
host_layout->addWidget(m_host_upnp, 0, 2);
#endif
host_layout->addWidget(m_host_games, 1, 0, 1, -1);
host_layout->addWidget(m_host_force_port_check, 2, 0);
host_layout->addWidget(m_host_force_port_box, 2, 1, Qt::AlignLeft);
host_layout->addWidget(m_host_button, 2, 2, Qt::AlignRight);
host_widget->setLayout(host_layout);
m_connection_type->addItem(tr("Direct Connection"));
m_connection_type->addItem(tr("Traversal Server"));
m_main_layout->addWidget(new QLabel(tr("Connection Type:")), 0, 0);
m_main_layout->addWidget(m_connection_type, 0, 1);
m_main_layout->addWidget(m_reset_traversal_button, 0, 2);
m_main_layout->addWidget(new QLabel(tr("Nickname:")), 1, 0);
m_main_layout->addWidget(m_nickname_edit, 1, 1);
m_main_layout->addWidget(m_tab_widget, 2, 0, 1, -1);
m_main_layout->addWidget(m_button_box, 3, 0, 1, -1);
// Tabs
m_tab_widget->addTab(connection_widget, tr("Connect"));
m_tab_widget->addTab(host_widget, tr("Host"));
setLayout(m_main_layout);
}
void NetPlaySetupDialog::ConnectWidgets()
{
connect(m_connection_type, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this, &NetPlaySetupDialog::OnConnectionTypeChanged);
connect(m_nickname_edit, &QLineEdit::textChanged, this, &NetPlaySetupDialog::SaveSettings);
// Connect widget
connect(m_ip_edit, &QLineEdit::textChanged, this, &NetPlaySetupDialog::SaveSettings);
connect(m_connect_port_box, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
&NetPlaySetupDialog::SaveSettings);
// Host widget
connect(m_host_port_box, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
&NetPlaySetupDialog::SaveSettings);
connect(m_host_games, static_cast<void (QListWidget::*)(int)>(&QListWidget::currentRowChanged),
[](int index) { QSettings().setValue(QStringLiteral("netplay/hostgame"), index); });
connect(m_host_force_port_check, &QCheckBox::toggled,
[this](int value) { m_host_force_port_box->setEnabled(value); });
#ifdef USE_UPNP
connect(m_host_upnp, &QCheckBox::stateChanged, this, &NetPlaySetupDialog::SaveSettings);
#endif
connect(m_connect_button, &QPushButton::clicked, this, &QDialog::accept);
connect(m_host_button, &QPushButton::clicked, this, &QDialog::accept);
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(m_reset_traversal_button, &QPushButton::clicked, this,
&NetPlaySetupDialog::ResetTraversalHost);
}
void NetPlaySetupDialog::SaveSettings()
{
Config::SetBaseOrCurrent(Config::NETPLAY_NICKNAME, m_nickname_edit->text().toStdString());
Config::SetBaseOrCurrent(Config::NETPLAY_HOST_CODE, m_ip_edit->text().toStdString());
Config::SetBaseOrCurrent(Config::NETPLAY_CONNECT_PORT,
static_cast<u16>(m_connect_port_box->value()));
Config::SetBaseOrCurrent(Config::NETPLAY_HOST_PORT, static_cast<u16>(m_host_port_box->value()));
Config::SetBaseOrCurrent(Config::NETPLAY_USE_UPNP, m_host_upnp->isChecked());
if (m_host_force_port_check->isChecked())
Config::SetBaseOrCurrent(Config::NETPLAY_LISTEN_PORT,
static_cast<u16>(m_host_force_port_box->value()));
}
void NetPlaySetupDialog::OnConnectionTypeChanged(int index)
{
m_connect_port_box->setHidden(index != 0);
m_connect_port_label->setHidden(index != 0);
m_host_port_label->setHidden(index != 0);
m_host_port_box->setHidden(index != 0);
m_host_upnp->setHidden(index != 0);
m_host_force_port_check->setHidden(index == 0);
m_host_force_port_box->setHidden(index == 0);
m_reset_traversal_button->setHidden(index == 0);
std::string address = Config::Get(Config::NETPLAY_HOST_CODE);
m_ip_label->setText(index == 0 ? tr("IP Address:") : tr("Host Code:"));
m_ip_edit->setText(QString::fromStdString(address));
Config::SetBaseOrCurrent(Config::NETPLAY_TRAVERSAL_CHOICE,
std::string(index == 0 ? "direct" : "traversal"));
}
void NetPlaySetupDialog::show()
{
PopulateGameList();
QDialog::show();
}
void NetPlaySetupDialog::accept()
{
SaveSettings();
if (m_tab_widget->currentIndex() == 0)
{
emit Join();
}
else
{
auto items = m_host_games->selectedItems();
if (items.size() == 0)
{
QMessageBox::critical(this, tr("Error"), tr("You must select a game to host!"));
return;
}
emit Host(items[0]->text());
}
}
void NetPlaySetupDialog::PopulateGameList()
{
m_host_games->clear();
for (int i = 0; i < m_game_list_model->rowCount(QModelIndex()); i++)
{
auto title = m_game_list_model->GetUniqueID(i);
auto path = m_game_list_model->GetPath(i);
auto* item = new QListWidgetItem(title);
item->setData(Qt::UserRole, path);
m_host_games->addItem(item);
}
m_host_games->sortItems();
}
void NetPlaySetupDialog::ResetTraversalHost()
{
Config::SetBaseOrCurrent(Config::NETPLAY_TRAVERSAL_SERVER,
Config::NETPLAY_TRAVERSAL_SERVER.default_value);
Config::SetBaseOrCurrent(Config::NETPLAY_TRAVERSAL_PORT,
Config::NETPLAY_TRAVERSAL_PORT.default_value);
QMessageBox::information(
this, tr("Reset Traversal Server"),
tr("Reset Traversal Server to %1:%2")
.arg(QString::fromStdString(Config::NETPLAY_TRAVERSAL_SERVER.default_value),
QString::number(Config::NETPLAY_TRAVERSAL_PORT.default_value)));
}

View File

@ -0,0 +1,72 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <QDialog>
class GameListModel;
class QCheckBox;
class QComboBox;
class QDialogButtonBox;
class QLabel;
class QLineEdit;
class QListWidget;
class QGridLayout;
class QPushButton;
class QSpinBox;
class QTabWidget;
class NetPlaySetupDialog : public QDialog
{
Q_OBJECT
public:
NetPlaySetupDialog(QWidget* parent);
void accept() override;
void show();
signals:
bool Join();
bool Host(const QString& game_identifier);
private:
void CreateMainLayout();
void ConnectWidgets();
void PopulateGameList();
void ResetTraversalHost();
void SaveSettings();
void OnConnectionTypeChanged(int index);
// Main Widget
QDialogButtonBox* m_button_box;
QComboBox* m_connection_type;
QLineEdit* m_nickname_edit;
QGridLayout* m_main_layout;
QTabWidget* m_tab_widget;
QPushButton* m_reset_traversal_button;
// Connection Widget
QLabel* m_ip_label;
QLineEdit* m_ip_edit;
QLabel* m_connect_port_label;
QSpinBox* m_connect_port_box;
QPushButton* m_connect_button;
// Host Widget
QLabel* m_host_port_label;
QSpinBox* m_host_port_box;
QListWidget* m_host_games;
QPushButton* m_host_button;
QCheckBox* m_host_force_port_check;
QSpinBox* m_host_force_port_box;
#ifdef USE_UPNP
QCheckBox* m_host_upnp;
#endif
GameListModel* m_game_list_model;
};

View File

@ -0,0 +1,94 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "DolphinQt2/NetPlay/PadMappingDialog.h"
#include "DolphinQt2/Settings.h"
#include "Core/NetPlayClient.h"
#include <QComboBox>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QLabel>
PadMappingDialog::PadMappingDialog(QWidget* parent) : QDialog(parent)
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
CreateWidgets();
ConnectWidgets();
}
void PadMappingDialog::CreateWidgets()
{
m_main_layout = new QGridLayout;
m_button_box = new QDialogButtonBox(QDialogButtonBox::Ok);
for (unsigned int i = 0; i < m_wii_boxes.size(); i++)
{
m_gc_boxes[i] = new QComboBox;
m_wii_boxes[i] = new QComboBox;
m_main_layout->addWidget(new QLabel(tr("GC Port %1").arg(i + 1)), 0, i);
m_main_layout->addWidget(new QLabel(tr("Wii Remote Port %1").arg(i + 1)), 0, 4 + i);
m_main_layout->addWidget(m_gc_boxes[i], 1, i);
m_main_layout->addWidget(m_wii_boxes[i], 1, 4 + i);
}
m_main_layout->addWidget(m_button_box, 2, 0, 1, -1);
setLayout(m_main_layout);
}
void PadMappingDialog::ConnectWidgets()
{
connect(m_button_box, &QDialogButtonBox::accepted, this, &QDialog::accept);
}
int PadMappingDialog::exec()
{
m_players = Settings::Instance().GetNetPlayClient()->GetPlayers();
QStringList players;
players.append(tr("None"));
for (const auto& player : m_players)
players.append(QString::fromStdString(player->name));
for (auto& combo_group : {m_gc_boxes, m_wii_boxes})
{
for (auto& combo : combo_group)
{
combo->clear();
combo->addItems(players);
connect(combo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
&PadMappingDialog::OnMappingChanged);
}
}
return QDialog::exec();
}
PadMappingArray PadMappingDialog::GetGCPadArray()
{
return m_pad_mapping;
}
PadMappingArray PadMappingDialog::GetWiimoteArray()
{
return m_wii_mapping;
}
void PadMappingDialog::OnMappingChanged()
{
for (unsigned int i = 0; i < m_wii_boxes.size(); i++)
{
int gc_id = m_gc_boxes[i]->currentIndex();
int wii_id = m_wii_boxes[i]->currentIndex();
m_pad_mapping[i] = gc_id > 0 ? m_players[gc_id - 1]->pid : -1;
m_wii_mapping[i] = wii_id > 0 ? m_players[wii_id - 1]->pid : -1;
}
}

View File

@ -0,0 +1,41 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <QDialog>
#include "Core/NetPlayProto.h"
class NetPlayClient;
class Player;
class QGridLayout;
class QComboBox;
class QDialogButtonBox;
class PadMappingDialog : public QDialog
{
Q_OBJECT
public:
explicit PadMappingDialog(QWidget* widget);
int exec();
PadMappingArray GetGCPadArray();
PadMappingArray GetWiimoteArray();
private:
void CreateWidgets();
void ConnectWidgets();
void OnMappingChanged();
PadMappingArray m_pad_mapping;
PadMappingArray m_wii_mapping;
QGridLayout* m_main_layout;
std::array<QComboBox*, 4> m_gc_boxes;
std::array<QComboBox*, 4> m_wii_boxes;
std::vector<const Player*> m_players;
QDialogButtonBox* m_button_box;
};

View File

@ -11,6 +11,7 @@
#include "Common/FileUtil.h"
#include "Common/StringUtil.h"
#include "Core/ConfigManager.h"
#include "DolphinQt2/GameList/GameListModel.h"
#include "DolphinQt2/Settings.h"
#include "InputCommon/InputConfig.h"
@ -171,3 +172,29 @@ void Settings::SetLogConfigVisible(bool visible)
emit LogConfigVisibilityChanged(visible);
}
}
GameListModel* Settings::GetGameListModel() const
{
static GameListModel* model = new GameListModel;
return model;
}
NetPlayClient* Settings::GetNetPlayClient()
{
return m_client.get();
}
void Settings::ResetNetPlayClient(NetPlayClient* client)
{
m_client.reset(client);
}
NetPlayServer* Settings::GetNetPlayServer()
{
return m_server.get();
}
void Settings::ResetNetPlayServer(NetPlayServer* server)
{
m_server.reset(server);
}

View File

@ -4,16 +4,22 @@
#pragma once
#include <memory>
#include <QObject>
#include <QVector>
#include "Common/NonCopyable.h"
#include "Core/NetPlayClient.h"
#include "Core/NetPlayServer.h"
namespace DiscIO
{
enum class Language;
}
class GameListModel;
class InputConfig;
// UI settings to be stored in the config directory.
@ -57,6 +63,15 @@ public:
void IncreaseVolume(int volume);
void DecreaseVolume(int volume);
// NetPlay
NetPlayClient* GetNetPlayClient();
void ResetNetPlayClient(NetPlayClient* client = nullptr);
NetPlayServer* GetNetPlayServer();
void ResetNetPlayServer(NetPlayServer* server = nullptr);
// Other
GameListModel* GetGameListModel() const;
signals:
void ThemeChanged();
void PathAdded(const QString&);
@ -68,5 +83,7 @@ signals:
void LogConfigVisibilityChanged(bool visible);
private:
std::unique_ptr<NetPlayClient> m_client;
std::unique_ptr<NetPlayServer> m_server;
Settings();
};