@@ -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,19 +28,22 @@
#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"
#include "DolphinQt2/Config/SettingsWindow.h"
#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)
@@ -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 @@ class MainWindow final : public QMainWindow
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 @@ class MainWindow final : public QMainWindow
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;
};
@@ -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);
@@ -38,6 +38,7 @@ class MenuBar final : public QMenuBar
void Fullscreen();
void FrameAdvance();
void Screenshot();
void StartNetPlay();
void StateLoad();
void StateSave();
void StateLoadSlot();
@@ -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();
}
@@ -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;
};
@@ -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();
}
@@ -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;
};

Large diffs are not rendered by default.

@@ -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;
};
@@ -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)));
}
@@ -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;
};
@@ -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;
}
}
@@ -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;
};
@@ -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);
}
@@ -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 @@ class Settings final : public QObject, NonCopyable
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 @@ class Settings final : public QObject, NonCopyable
void LogConfigVisibilityChanged(bool visible);

private:
std::unique_ptr<NetPlayClient> m_client;
std::unique_ptr<NetPlayServer> m_server;
Settings();
};