dolphin/Source/Core/DolphinQt2/NetPlay/NetPlayDialog.cpp
Lioncash 6f473b96d0 PowerPC: Convert CPUCore enum into an enum class
Makes the enum values strongly-typed and prevents the identifiers from
polluting the PowerPC namespace. This also cleans up the parameters of
some functions where we were accepting an ambiguous int type and
expecting the correct values to be passed in.

Now those parameters accept a PowerPC::CPUCore type only, making it
immediately obvious which values should be passed in. It also turns out
we were storing these core types into other structures as plain ints,
which have also been corrected.

As this type is used directly with the configuration code, we need to
provide our own overloaded insertion (<<) and extraction (>>) operators
in order to make it compatible with it. These are fairly trivial to
implement, so there's no issue here.

A minor adjustment to TryParse() was required, as our generic function
was doing the following:

N tmp = 0;

which is problematic, as custom types may not be able to have that
assignment performed (e.g. strongly-typed enums), so we change this to:

N tmp;

which is sufficient, as the value is attempted to be initialized
immediately under that statement.
2018-06-15 10:27:59 -04:00

642 lines
20 KiB
C++

// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "DolphinQt2/NetPlay/NetPlayDialog.h"
#include <QAction>
#include <QApplication>
#include <QCheckBox>
#include <QClipboard>
#include <QComboBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QLineEdit>
#include <QMenu>
#include <QMessageBox>
#include <QProgressDialog>
#include <QPushButton>
#include <QSpinBox>
#include <QSplitter>
#include <QTableWidget>
#include <QTextBrowser>
#include <QToolButton>
#include <sstream>
#include "Common/CommonPaths.h"
#include "Common/Config/Config.h"
#include "Common/TraversalClient.h"
#include "Core/Config/SYSCONFSettings.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("NetPlay"));
m_pad_mapping = new PadMappingDialog(this);
m_md5_dialog = new MD5Dialog(this);
CreateChatLayout();
CreatePlayersLayout();
CreateMainLayout();
ConnectWidgets();
auto& settings = Settings::Instance().GetQSettings();
restoreGeometry(settings.value(QStringLiteral("netplaydialog/geometry")).toByteArray());
m_splitter->restoreState(settings.value(QStringLiteral("netplaydialog/splitter")).toByteArray());
}
NetPlayDialog::~NetPlayDialog()
{
auto& settings = Settings::Instance().GetQSettings();
settings.setValue(QStringLiteral("netplaydialog/geometry"), saveGeometry());
settings.setValue(QStringLiteral("netplaydialog/splitter"), m_splitter->saveState());
}
void NetPlayDialog::CreateMainLayout()
{
m_main_layout = new QGridLayout;
m_game_button = new QPushButton;
m_md5_button = new QToolButton;
m_start_button = new QPushButton(tr("Start"));
m_buffer_size_box = new QSpinBox;
m_save_sd_box = new QCheckBox(tr("Write save/SD 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_splitter = new QSplitter(Qt::Horizontal);
m_game_button->setDefault(false);
m_game_button->setAutoDefault(false);
auto* default_button = new QAction(tr("Calculate MD5 hash"), m_md5_button);
auto* menu = new QMenu(this);
auto* other_game_button = new QAction(tr("Other game"), this);
auto* sdcard_button = new QAction(tr("SD Card"), this);
menu->addAction(other_game_button);
menu->addAction(sdcard_button);
connect(default_button, &QAction::triggered,
[this] { Settings::Instance().GetNetPlayServer()->ComputeMD5(m_current_game); });
connect(other_game_button, &QAction::triggered, [this] {
GameListDialog gld(this);
if (gld.exec() == QDialog::Accepted)
{
Settings::Instance().GetNetPlayServer()->ComputeMD5(gld.GetSelectedUniqueID().toStdString());
}
});
connect(sdcard_button, &QAction::triggered,
[] { Settings::Instance().GetNetPlayServer()->ComputeMD5(WII_SDCARD); });
m_md5_button->setDefaultAction(default_button);
m_md5_button->setPopupMode(QToolButton::MenuButtonPopup);
m_md5_button->setMenu(menu);
m_main_layout->addWidget(m_game_button, 0, 0);
m_main_layout->addWidget(m_md5_button, 0, 1);
m_main_layout->addWidget(m_splitter, 1, 0, 1, -1);
m_splitter->addWidget(m_chat_box);
m_splitter->addWidget(m_players_box);
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 QTextBrowser;
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 QTableWidget;
m_kick_button = new QPushButton(tr("Kick Player"));
m_assign_ports_button = new QPushButton(tr("Assign Controller Ports"));
m_players_list->setColumnCount(5);
m_players_list->verticalHeader()->hide();
m_players_list->setSelectionBehavior(QAbstractItemView::SelectRows);
m_players_list->horizontalHeader()->setStretchLastSection(true);
for (int i = 0; i < 4; i++)
m_players_list->horizontalHeader()->setSectionResizeMode(i, QHeaderView::ResizeToContents);
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, &QTableWidget::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 (value == m_buffer_size)
return;
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_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(&Settings::Instance(), &Settings::EmulationStateChanged, this, [=](Core::State state) {
if (state == Core::State::Uninitialized && 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).toHtmlEscaped(),
QString::fromStdString(msg).toHtmlEscaped()),
"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.cpu_core;
settings.m_EnableCheats = instance.bEnableCheats;
settings.m_SelectedLanguage = instance.SelectedLanguage;
settings.m_OverrideGCLanguage = instance.bOverrideGCLanguage;
settings.m_ProgressiveScan = Config::Get(Config::SYSCONF_PROGRESSIVE_SCAN);
settings.m_PAL60 = Config::Get(Config::SYSCONF_PAL60);
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::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_buffer_size = 0;
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())
{
const auto interface = QString::fromStdString(iface);
m_room_box->addItem(iface == "!local!" ? tr("Local") : interface, interface);
}
}
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_button->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()
{
auto* client = Settings::Instance().GetNetPlayClient();
// Update Player List
const auto players = client->GetPlayers();
const auto player_count = static_cast<int>(players.size());
int selection_pid = m_players_list->currentItem() ?
m_players_list->currentItem()->data(Qt::UserRole).toInt() :
-1;
m_players_list->clear();
m_players_list->setHorizontalHeaderLabels(
{tr("Player"), tr("Game Status"), tr("Ping"), tr("Mapping"), tr("Revision")});
m_players_list->setRowCount(player_count);
const auto get_mapping_string = [](const Player* player, const PadMappingArray& array) {
std::string str;
for (size_t i = 0; i < array.size(); i++)
{
if (player->pid == array[i])
str += std::to_string(i + 1);
else
str += '-';
}
return '|' + str + '|';
};
static const std::map<PlayerGameStatus, QString> player_status{
{PlayerGameStatus::Ok, tr("OK")}, {PlayerGameStatus::NotFound, tr("Not Found")}};
for (int i = 0; i < player_count; i++)
{
const auto* p = players[i];
auto* name_item = new QTableWidgetItem(QString::fromStdString(p->name));
auto* status_item = new QTableWidgetItem(player_status.count(p->game_status) ?
player_status.at(p->game_status) :
QStringLiteral("?"));
auto* ping_item = new QTableWidgetItem(QStringLiteral("%1 ms").arg(p->ping));
auto* mapping_item = new QTableWidgetItem(
QString::fromStdString(get_mapping_string(p, client->GetPadMapping()) +
get_mapping_string(p, client->GetWiimoteMapping())));
auto* revision_item = new QTableWidgetItem(QString::fromStdString(p->revision));
for (auto* item : {name_item, status_item, ping_item, mapping_item, revision_item})
{
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
item->setData(Qt::UserRole, static_cast<int>(p->pid));
}
m_players_list->setItem(i, 0, name_item);
m_players_list->setItem(i, 1, status_item);
m_players_list->setItem(i, 2, ping_item);
m_players_list->setItem(i, 3, mapping_item);
m_players_list->setItem(i, 4, revision_item);
if (p->pid == selection_pid)
m_players_list->selectRow(i);
}
// Update Room ID / IP label
if (m_use_traversal && m_room_box->currentIndex() == 0)
{
switch (g_TraversalClient->GetState())
{
case TraversalClient::Connecting:
m_hostcode_label->setText(tr("..."));
m_hostcode_action_button->setEnabled(false);
break;
case TraversalClient::Connected:
{
const auto host_id = g_TraversalClient->GetHostID();
m_hostcode_label->setText(
QString::fromStdString(std::string(host_id.begin(), host_id.end())));
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->currentData().toString().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, &NetPlayDialog::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).toHtmlEscaped(), "");
}
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), "magenta");
}
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("Buffer size changed to %1").arg(buffer), "");
m_buffer_size = static_cast<int>(buffer);
}
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(TraversalClient::FailureReason error)
{
QueueOnObject(this, [this, error] {
switch (error)
{
case TraversalClient::FailureReason::BadHost:
QMessageBox::critical(this, tr("Traversal Error"), tr("Couldn't look up central server"));
QDialog::reject();
break;
case TraversalClient::FailureReason::VersionTooOld:
QMessageBox::critical(this, tr("Traversal Error"),
tr("Dolphin is too old for traversal server"));
QDialog::reject();
break;
case TraversalClient::FailureReason::ServerForgotAboutUs:
case TraversalClient::FailureReason::SocketSendError:
case TraversalClient::FailureReason::ResendTimeout:
UpdateGUI();
break;
}
});
}
bool NetPlayDialog::IsRecording()
{
std::optional<bool> is_recording = RunOnObject(m_record_input_box, &QCheckBox::isChecked);
if (is_recording)
return *is_recording;
return false;
}
std::string NetPlayDialog::FindGame(const std::string& game)
{
std::optional<std::string> path = RunOnObject(this, [this, game] {
for (int i = 0; i < m_game_list_model->rowCount(QModelIndex()); i++)
{
if (m_game_list_model->GetUniqueIdentifier(i).toStdString() == game)
return m_game_list_model->GetPath(i).toStdString();
}
return std::string("");
});
if (path)
return *path;
return std::string("");
}
void NetPlayDialog::ShowMD5Dialog(const std::string& file_identifier)
{
QueueOnObject(this, [this, file_identifier] {
m_md5_button->setEnabled(false);
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_button->setEnabled(true);
});
}
void NetPlayDialog::AbortMD5()
{
QueueOnObject(this, [this] {
m_md5_dialog->close();
m_md5_button->setEnabled(true);
});
}