NetPlayBrowser.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. // Copyright 2019 Dolphin Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include "DolphinQt/NetPlay/NetPlayBrowser.h"
  4. #include <QCheckBox>
  5. #include <QComboBox>
  6. #include <QDialogButtonBox>
  7. #include <QGridLayout>
  8. #include <QGroupBox>
  9. #include <QHeaderView>
  10. #include <QInputDialog>
  11. #include <QLabel>
  12. #include <QLineEdit>
  13. #include <QPushButton>
  14. #include <QRadioButton>
  15. #include <QSpacerItem>
  16. #include <QTableWidget>
  17. #include <QTableWidgetItem>
  18. #include <QVBoxLayout>
  19. #include "Common/Version.h"
  20. #include "Core/Config/NetplaySettings.h"
  21. #include "Core/ConfigManager.h"
  22. #include "DolphinQt/QtUtils/ModalMessageBox.h"
  23. #include "DolphinQt/QtUtils/NonDefaultQPushButton.h"
  24. #include "DolphinQt/QtUtils/SetWindowDecorations.h"
  25. #include "DolphinQt/Settings.h"
  26. NetPlayBrowser::NetPlayBrowser(QWidget* parent) : QDialog(parent)
  27. {
  28. setWindowTitle(tr("NetPlay Session Browser"));
  29. setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
  30. CreateWidgets();
  31. RestoreSettings();
  32. ConnectWidgets();
  33. resize(750, 500);
  34. m_table_widget->verticalHeader()->setHidden(true);
  35. m_table_widget->setAlternatingRowColors(true);
  36. m_refresh_run.Set(true);
  37. m_refresh_thread = std::thread([this] { RefreshLoop(); });
  38. UpdateList();
  39. Refresh();
  40. }
  41. NetPlayBrowser::~NetPlayBrowser()
  42. {
  43. m_refresh_run.Set(false);
  44. m_refresh_event.Set();
  45. if (m_refresh_thread.joinable())
  46. m_refresh_thread.join();
  47. SaveSettings();
  48. }
  49. void NetPlayBrowser::CreateWidgets()
  50. {
  51. auto* layout = new QVBoxLayout;
  52. m_table_widget = new QTableWidget;
  53. m_table_widget->setTabKeyNavigation(false);
  54. m_table_widget->setSelectionBehavior(QAbstractItemView::SelectRows);
  55. m_table_widget->setSelectionMode(QAbstractItemView::SingleSelection);
  56. m_table_widget->setWordWrap(false);
  57. m_region_combo = new QComboBox;
  58. m_region_combo->addItem(tr("Any Region"));
  59. for (const auto& region : NetPlayIndex::GetRegions())
  60. {
  61. m_region_combo->addItem(
  62. tr("%1 (%2)").arg(tr(region.second.c_str())).arg(QString::fromStdString(region.first)),
  63. QString::fromStdString(region.first));
  64. }
  65. m_region_combo->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
  66. m_status_label = new QLabel;
  67. m_button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
  68. m_button_refresh = new NonDefaultQPushButton(tr("Refresh"));
  69. m_edit_name = new QLineEdit;
  70. m_edit_game_id = new QLineEdit;
  71. m_check_hide_incompatible = new QCheckBox(tr("Hide Incompatible Sessions"));
  72. m_check_hide_ingame = new QCheckBox(tr("Hide In-Game Sessions"));
  73. m_check_hide_incompatible->setChecked(true);
  74. m_radio_all = new QRadioButton(tr("Private and Public"));
  75. m_radio_private = new QRadioButton(tr("Private"));
  76. m_radio_public = new QRadioButton(tr("Public"));
  77. m_radio_all->setChecked(true);
  78. auto* filter_box = new QGroupBox(tr("Filters"));
  79. auto* filter_layout = new QGridLayout;
  80. filter_box->setLayout(filter_layout);
  81. filter_layout->addWidget(new QLabel(tr("Region:")), 0, 0);
  82. filter_layout->addWidget(m_region_combo, 0, 1, 1, -1);
  83. filter_layout->addWidget(new QLabel(tr("Name:")), 1, 0);
  84. filter_layout->addWidget(m_edit_name, 1, 1, 1, -1);
  85. filter_layout->addWidget(new QLabel(tr("Game ID:")), 2, 0);
  86. filter_layout->addWidget(m_edit_game_id, 2, 1, 1, -1);
  87. filter_layout->addWidget(m_radio_all, 3, 1);
  88. filter_layout->addWidget(m_radio_public, 3, 2);
  89. filter_layout->addWidget(m_radio_private, 3, 3);
  90. filter_layout->addItem(new QSpacerItem(4, 1, QSizePolicy::Expanding), 3, 4);
  91. filter_layout->addWidget(m_check_hide_incompatible, 4, 1, 1, -1);
  92. filter_layout->addWidget(m_check_hide_ingame, 5, 1, 1, -1);
  93. layout->addWidget(m_table_widget);
  94. layout->addWidget(filter_box);
  95. layout->addWidget(m_status_label);
  96. layout->addWidget(m_button_box);
  97. m_button_box->addButton(m_button_refresh, QDialogButtonBox::ResetRole);
  98. m_button_box->button(QDialogButtonBox::Ok)->setEnabled(false);
  99. setLayout(layout);
  100. }
  101. void NetPlayBrowser::ConnectWidgets()
  102. {
  103. connect(m_region_combo, &QComboBox::currentIndexChanged, this, &NetPlayBrowser::Refresh);
  104. connect(m_button_box, &QDialogButtonBox::accepted, this, &NetPlayBrowser::accept);
  105. connect(m_button_box, &QDialogButtonBox::rejected, this, &NetPlayBrowser::reject);
  106. connect(m_button_refresh, &QPushButton::clicked, this, &NetPlayBrowser::Refresh);
  107. connect(m_radio_all, &QRadioButton::toggled, this, &NetPlayBrowser::Refresh);
  108. connect(m_radio_private, &QRadioButton::toggled, this, &NetPlayBrowser::Refresh);
  109. connect(m_check_hide_incompatible, &QRadioButton::toggled, this, &NetPlayBrowser::Refresh);
  110. connect(m_check_hide_ingame, &QRadioButton::toggled, this, &NetPlayBrowser::Refresh);
  111. connect(m_edit_name, &QLineEdit::textChanged, this, &NetPlayBrowser::Refresh);
  112. connect(m_edit_game_id, &QLineEdit::textChanged, this, &NetPlayBrowser::Refresh);
  113. connect(m_table_widget, &QTableWidget::itemSelectionChanged, this,
  114. &NetPlayBrowser::OnSelectionChanged);
  115. connect(m_table_widget, &QTableWidget::itemDoubleClicked, this, &NetPlayBrowser::accept);
  116. connect(this, &NetPlayBrowser::UpdateStatusRequested, this,
  117. &NetPlayBrowser::OnUpdateStatusRequested, Qt::QueuedConnection);
  118. connect(this, &NetPlayBrowser::UpdateListRequested, this, &NetPlayBrowser::OnUpdateListRequested,
  119. Qt::QueuedConnection);
  120. }
  121. void NetPlayBrowser::Refresh()
  122. {
  123. std::map<std::string, std::string> filters;
  124. if (m_check_hide_incompatible->isChecked())
  125. filters["version"] = Common::GetScmDescStr();
  126. if (!m_edit_name->text().isEmpty())
  127. filters["name"] = m_edit_name->text().toStdString();
  128. if (!m_edit_game_id->text().isEmpty())
  129. filters["game"] = m_edit_game_id->text().toStdString();
  130. if (!m_radio_all->isChecked())
  131. filters["password"] = std::to_string(m_radio_private->isChecked());
  132. if (m_region_combo->currentIndex() != 0)
  133. filters["region"] = m_region_combo->currentData().toString().toStdString();
  134. if (m_check_hide_ingame->isChecked())
  135. filters["in_game"] = "0";
  136. std::unique_lock<std::mutex> lock(m_refresh_filters_mutex);
  137. m_refresh_filters = std::move(filters);
  138. m_refresh_event.Set();
  139. }
  140. void NetPlayBrowser::RefreshLoop()
  141. {
  142. while (m_refresh_run.IsSet())
  143. {
  144. m_refresh_event.Wait();
  145. std::unique_lock<std::mutex> lock(m_refresh_filters_mutex);
  146. if (m_refresh_filters)
  147. {
  148. auto filters = std::move(*m_refresh_filters);
  149. m_refresh_filters.reset();
  150. lock.unlock();
  151. emit UpdateStatusRequested(tr("Refreshing..."));
  152. NetPlayIndex client;
  153. auto entries = client.List(filters);
  154. if (entries)
  155. {
  156. emit UpdateListRequested(std::move(*entries));
  157. }
  158. else
  159. {
  160. emit UpdateStatusRequested(tr("Error obtaining session list: %1")
  161. .arg(QString::fromStdString(client.GetLastError())));
  162. }
  163. }
  164. }
  165. }
  166. void NetPlayBrowser::UpdateList()
  167. {
  168. const int session_count = static_cast<int>(m_sessions.size());
  169. m_table_widget->clear();
  170. m_table_widget->setColumnCount(7);
  171. m_table_widget->setHorizontalHeaderLabels({tr("Region"), tr("Name"), tr("Password?"),
  172. tr("In-Game?"), tr("Game"), tr("Players"),
  173. tr("Version")});
  174. auto* hor_header = m_table_widget->horizontalHeader();
  175. hor_header->setSectionResizeMode(0, QHeaderView::ResizeToContents);
  176. hor_header->setSectionResizeMode(1, QHeaderView::Stretch);
  177. hor_header->setSectionResizeMode(2, QHeaderView::ResizeToContents);
  178. hor_header->setSectionResizeMode(3, QHeaderView::ResizeToContents);
  179. hor_header->setSectionResizeMode(4, QHeaderView::Stretch);
  180. hor_header->setHighlightSections(false);
  181. m_table_widget->setRowCount(session_count);
  182. for (int i = 0; i < session_count; i++)
  183. {
  184. const auto& entry = m_sessions[i];
  185. auto* region = new QTableWidgetItem(QString::fromStdString(entry.region));
  186. auto* name = new QTableWidgetItem(QString::fromStdString(entry.name));
  187. auto* password = new QTableWidgetItem(entry.has_password ? tr("Yes") : tr("No"));
  188. auto* in_game = new QTableWidgetItem(entry.in_game ? tr("Yes") : tr("No"));
  189. auto* game_id = new QTableWidgetItem(QString::fromStdString(entry.game_id));
  190. auto* player_count = new QTableWidgetItem(QStringLiteral("%1").arg(entry.player_count));
  191. auto* version = new QTableWidgetItem(QString::fromStdString(entry.version));
  192. const bool enabled = Common::GetScmDescStr() == entry.version;
  193. for (const auto& item : {region, name, password, in_game, game_id, player_count, version})
  194. item->setFlags(enabled ? Qt::ItemIsEnabled | Qt::ItemIsSelectable : Qt::NoItemFlags);
  195. m_table_widget->setItem(i, 0, region);
  196. m_table_widget->setItem(i, 1, name);
  197. m_table_widget->setItem(i, 2, password);
  198. m_table_widget->setItem(i, 3, in_game);
  199. m_table_widget->setItem(i, 4, game_id);
  200. m_table_widget->setItem(i, 5, player_count);
  201. m_table_widget->setItem(i, 6, version);
  202. }
  203. m_status_label->setText(
  204. (session_count == 1 ? tr("%1 session found") : tr("%1 sessions found")).arg(session_count));
  205. }
  206. void NetPlayBrowser::OnSelectionChanged()
  207. {
  208. m_button_box->button(QDialogButtonBox::Ok)
  209. ->setEnabled(!m_table_widget->selectedItems().isEmpty());
  210. }
  211. void NetPlayBrowser::OnUpdateStatusRequested(const QString& status)
  212. {
  213. m_status_label->setText(status);
  214. }
  215. void NetPlayBrowser::OnUpdateListRequested(std::vector<NetPlaySession> sessions)
  216. {
  217. m_sessions = std::move(sessions);
  218. UpdateList();
  219. }
  220. void NetPlayBrowser::accept()
  221. {
  222. if (m_table_widget->selectedItems().isEmpty())
  223. return;
  224. const int index = m_table_widget->selectedItems()[0]->row();
  225. NetPlaySession& session = m_sessions[index];
  226. std::string server_id = session.server_id;
  227. if (m_sessions[index].has_password)
  228. {
  229. QInputDialog dialog(this);
  230. dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint);
  231. dialog.setWindowTitle(tr("Enter password"));
  232. dialog.setLabelText(tr("This session requires a password:"));
  233. dialog.setWindowModality(Qt::WindowModal);
  234. dialog.setTextEchoMode(QLineEdit::Password);
  235. SetQWidgetWindowDecorations(&dialog);
  236. if (dialog.exec() != QDialog::Accepted)
  237. return;
  238. const std::string password = dialog.textValue().toStdString();
  239. auto decrypted_id = session.DecryptID(password);
  240. if (!decrypted_id)
  241. {
  242. ModalMessageBox::warning(this, tr("Error"), tr("Invalid password provided."));
  243. return;
  244. }
  245. server_id = decrypted_id.value();
  246. }
  247. QDialog::accept();
  248. Config::SetBaseOrCurrent(Config::NETPLAY_TRAVERSAL_CHOICE, session.method);
  249. Config::SetBaseOrCurrent(Config::NETPLAY_CONNECT_PORT, session.port);
  250. if (session.method == "traversal")
  251. Config::SetBaseOrCurrent(Config::NETPLAY_HOST_CODE, server_id);
  252. else
  253. Config::SetBaseOrCurrent(Config::NETPLAY_ADDRESS, server_id);
  254. emit Join();
  255. }
  256. void NetPlayBrowser::SaveSettings() const
  257. {
  258. auto& settings = Settings::Instance().GetQSettings();
  259. settings.setValue(QStringLiteral("netplaybrowser/geometry"), saveGeometry());
  260. settings.setValue(QStringLiteral("netplaybrowser/region"), m_region_combo->currentText());
  261. settings.setValue(QStringLiteral("netplaybrowser/name"), m_edit_name->text());
  262. settings.setValue(QStringLiteral("netplaybrowser/game_id"), m_edit_game_id->text());
  263. QString visibility(QStringLiteral("all"));
  264. if (m_radio_public->isChecked())
  265. visibility = QStringLiteral("public");
  266. else if (m_radio_private->isChecked())
  267. visibility = QStringLiteral("private");
  268. settings.setValue(QStringLiteral("netplaybrowser/visibility"), visibility);
  269. settings.setValue(QStringLiteral("netplaybrowser/hide_incompatible"),
  270. m_check_hide_incompatible->isChecked());
  271. settings.setValue(QStringLiteral("netplaybrowser/hide_ingame"), m_check_hide_ingame->isChecked());
  272. }
  273. void NetPlayBrowser::RestoreSettings()
  274. {
  275. const auto& settings = Settings::Instance().GetQSettings();
  276. const QByteArray geometry =
  277. settings.value(QStringLiteral("netplaybrowser/geometry")).toByteArray();
  278. if (!geometry.isEmpty())
  279. restoreGeometry(geometry);
  280. const QString region = settings.value(QStringLiteral("netplaybrowser/region")).toString();
  281. const bool valid_region = m_region_combo->findText(region) != -1;
  282. if (valid_region)
  283. m_region_combo->setCurrentText(region);
  284. m_edit_name->setText(settings.value(QStringLiteral("netplaybrowser/name")).toString());
  285. m_edit_game_id->setText(settings.value(QStringLiteral("netplaybrowser/game_id")).toString());
  286. const QString visibility = settings.value(QStringLiteral("netplaybrowser/visibility")).toString();
  287. if (visibility == QStringLiteral("public"))
  288. m_radio_public->setChecked(true);
  289. else if (visibility == QStringLiteral("private"))
  290. m_radio_private->setChecked(true);
  291. m_check_hide_incompatible->setChecked(
  292. settings.value(QStringLiteral("netplaybrowser/hide_incompatible"), true).toBool());
  293. m_check_hide_ingame->setChecked(
  294. settings.value(QStringLiteral("netplaybrowser/hide_ingame")).toBool());
  295. }