23 #include "serverlist.h"
25 #include "configuration/doomseekerconfig.h"
26 #include "gui/commongui.h"
27 #include "gui/mainwindow.h"
28 #include "gui/models/serverlistcolumn.h"
29 #include "gui/models/serverlistmodel.h"
30 #include "gui/models/serverlistproxymodel.h"
31 #include "gui/remoteconsole.h"
32 #include "gui/widgets/serverlistcontextmenu.h"
33 #include "gui/widgets/serverlistview.h"
34 #include "refresher/refresher.h"
35 #include "serverapi/server.h"
36 #include "serverapi/tooltips/servertooltip.h"
37 #include "serverapi/tooltips/tooltiprenderhint.h"
38 #include "urlopener.h"
42 #include <QHeaderView>
43 #include <QMessageBox>
47 using namespace ServerListColumnId;
50 : mainWindow(pMainWindow), model(nullptr), needsCleaning(false),
51 proxyModel(nullptr), sortOrder(Qt::AscendingOrder),
52 sortIndex(-1), table(serverTable)
58 ServerList::~ServerList()
60 saveColumnsWidthsSettings();
65 gConfig.serverFilter.info = filterInfo;
70 bool ServerList::areColumnsWidthsSettingsChanged()
72 for (
int i = 0; i < NUM_SERVERLIST_COLUMNS; ++i)
74 if (ServerListColumns::columns[i].width != table->columnWidth(i))
81 void ServerList::cleanUp()
87 void ServerList::cleanUpRightNow()
89 if (mainWindow->isEffectivelyActiveWindow())
93 void ServerList::cleanUpForce()
95 if (table ==
nullptr || table->model() ==
nullptr)
101 pModel->invalidate();
102 pModel->sortServers(sortIndex, sortOrder);
106 needsCleaning =
false;
109 void ServerList::clearAdditionalSorting()
111 proxyModel->clearAdditionalSorting();
114 void ServerList::columnHeaderClicked(
int index)
116 if (isSortingByColumn(index))
117 sortOrder = swappedCurrentSortOrder();
119 sortOrder = getColumnDefaultSortOrder(index);
124 QHeaderView *header = table->horizontalHeader();
125 header->setSortIndicator(sortIndex, sortOrder);
128 void ServerList::connectTableModelProxySlots()
130 QHeaderView *header = table->horizontalHeader();
131 this->connect(header, SIGNAL(sectionClicked(
int)), SLOT(columnHeaderClicked(
int)));
133 this->connect(table->selectionModel(),
134 SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
135 SLOT(itemSelected(QItemSelection)));
136 this->connect(table, SIGNAL(middleMouseClicked(QModelIndex,QPoint)),
137 SLOT(tableMiddleClicked()));
138 connect(table, &QWidget::customContextMenuRequested,
139 this, &ServerList::showContextMenu);
140 this->connect(table, SIGNAL(entered(QModelIndex)), SLOT(mouseEntered(QModelIndex)));
141 this->connect(table, SIGNAL(leftMouseDoubleClicked(QModelIndex,QPoint)),
142 SLOT(doubleClicked(QModelIndex)));
145 void ServerList::contextMenuAboutToHide()
147 sender()->deleteLater();
150 void ServerList::contextMenuTriggered(QAction *action)
153 ServerPtr server = contextMenu->server();
158 emit serverFilterModified(contextMenu->serverFilter());
161 switch (contextMenuResult)
167 case ServerListContextMenu::FindMissingWADs:
171 case ServerListContextMenu::Join:
172 emit serverDoubleClicked(server);
175 case ServerListContextMenu::OpenRemoteConsole:
179 case ServerListContextMenu::OpenURL:
183 UrlOpener::instance()->open(server->webSite());
190 case ServerListContextMenu::Refresh:
194 case ServerListContextMenu::ShowJoinCommandLine:
198 case ServerListContextMenu::SortAdditionallyAscending:
199 sortAdditionally(contextMenu->modelIndex(), Qt::AscendingOrder);
202 case ServerListContextMenu::SortAdditionallyDescending:
203 sortAdditionally(contextMenu->modelIndex(), Qt::DescendingOrder);
206 case ServerListContextMenu::RemoveAdditionalSortingForColumn:
207 removeAdditionalSortingForColumn(contextMenu->modelIndex());
210 case ServerListContextMenu::ClearAdditionalSorting:
211 clearAdditionalSorting();
214 case ServerListContextMenu::TogglePinServers:
215 for (
const ServerPtr &server : contextMenu->servers())
217 model->
redraw(server.data());
222 QMessageBox::warning(mainWindow, tr(
"Doomseeker - context menu warning"),
223 tr(
"Unhandled behavior in ServerList::contextMenuTriggered()"));
231 serverListModel->prepareHeaders();
232 return serverListModel;
238 this->connect(proxy, SIGNAL(additionalSortColumnsChanged()),
239 SLOT(updateHeaderTitles()));
240 this->connect(proxy, SIGNAL(additionalSortColumnsChanged()),
241 SLOT(saveAdditionalSortingConfig()));
242 proxy->setSourceModel(serverListModel);
243 proxy->setSortRole(ServerListModel::SLDT_SORT);
244 proxy->setSortCaseSensitivity(Qt::CaseInsensitive);
245 proxy->setFilterKeyColumn(IDServerName);
250 void ServerList::doubleClicked(
const QModelIndex &index)
252 emit serverDoubleClicked(serverFromIndex(index));
255 Qt::SortOrder ServerList::getColumnDefaultSortOrder(
int columnId)
258 return ServerListColumns::columns[columnId].defaultSortOrder;
261 bool ServerList::hasAtLeastOneServer()
const
263 return model->rowCount() > 0;
266 void ServerList::initCleanerTimer()
268 cleanerTimer.setInterval(200);
269 cleanerTimer.start();
270 connect(&cleanerTimer, SIGNAL(timeout()),
this, SLOT (cleanUp()));
273 bool ServerList::isAnyColumnSortedAdditionally()
const
275 return proxyModel->isAnyColumnSortedAdditionally();
278 bool ServerList::isSortingAdditionallyByColumn(
int column)
const
280 return proxyModel->isSortingAdditionallyByColumn(column);
283 bool ServerList::isSortingByColumn(
int columnIndex)
285 return sortIndex == columnIndex;
288 void ServerList::itemSelected(
const QItemSelection &selection)
290 auto pModel =
static_cast<QSortFilterProxyModel *
>(table->model());
291 QModelIndexList indexList = selection.indexes();
293 QList<ServerPtr> servers;
294 for (
int i = 0; i < indexList.count(); ++i)
296 QModelIndex realIndex = pModel->mapToSource(indexList[i]);
297 ServerPtr server = model->serverFromList(realIndex);
298 servers.append(server);
300 emit serversSelected(servers);
305 for (
int i = 0; i < model->rowCount(); ++i)
307 ServerPtr server = model->serverFromList(i);
308 server->lookupHost();
312 void ServerList::mouseEntered(
const QModelIndex &index)
314 auto pModel =
static_cast<QSortFilterProxyModel *
>(table->model());
315 QModelIndex realIndex = pModel->mapToSource(index);
316 ServerPtr server = model->serverFromList(realIndex);
320 renderHint.setFont(QToolTip::font());
323 if (screen !=
nullptr)
325 renderHint.setBoundingRect(screen->geometry());
331 switch (index.column())
334 tooltip = ServerTooltip::createPortToolTip(server);
338 tooltip = server->hostName(
true);
342 tooltip = ServerTooltip::createPlayersToolTip(renderHint, server);
346 tooltip = ServerTooltip::createServerNameToolTip(server);
350 tooltip = ServerTooltip::createIwadToolTip(server);
354 tooltip = ServerTooltip::createPwadsToolTip(server);
362 QToolTip::showText(QCursor::pos(), tooltip,
nullptr);
365 void ServerList::prepareServerTable()
367 model = createModel();
368 proxyModel = createSortingProxy(model);
370 columnHeaderClicked(IDPlayers);
371 table->setModel(proxyModel);
372 table->setContextMenuPolicy(Qt::CustomContextMenu);
373 table->setupTableProperties();
375 if (gConfig.doomseeker.serverListSortIndex >= 0)
377 sortIndex = gConfig.doomseeker.serverListSortIndex;
378 sortOrder =
static_cast<Qt::SortOrder
>(gConfig.doomseeker.serverListSortDirection);
381 connectTableModelProxySlots();
382 proxyModel->setAdditionalSortColumns(gConfig.doomseeker.additionalSortColumns());
385 void ServerList::redraw()
390 void ServerList::refreshSelected()
392 for (
const ServerPtr &server : selectedServers())
394 gRefresher->registerServer(server);
398 void ServerList::registerServer(ServerPtr server)
401 if (serverOnList !=
nullptr)
403 serverOnList->setCustom(server->isCustom() || serverOnList->isCustom());
404 model->
redraw(serverOnList.data());
405 gRefresher->registerServer(serverOnList);
409 this->connect(server.data(), SIGNAL(updated(ServerPtr,
int)),
410 SLOT(onServerUpdated(ServerPtr)));
411 this->connect(server.data(), SIGNAL(begunRefreshing(ServerPtr)),
412 SLOT(onServerBegunRefreshing(ServerPtr)));
414 emit serverRegistered(server);
415 gRefresher->registerServer(server);
419 void ServerList::removeServer(
const ServerPtr &server)
421 server->disconnect(
this);
422 model->removeServer(server);
423 emit serverDeregistered(server);
426 void ServerList::removeCustomServers()
428 for (ServerPtr server : model->customServers())
430 removeServer(server);
434 void ServerList::removeNonSpecialServers()
436 for (ServerPtr server : model->nonSpecialServers())
438 removeServer(server);
442 void ServerList::removeAdditionalSortingForColumn(
const QModelIndex &modelIndex)
444 proxyModel->removeAdditionalColumnSorting(modelIndex.column());
447 void ServerList::saveAdditionalSortingConfig()
449 gConfig.doomseeker.setAdditionalSortColumns(proxyModel->additionalSortColumns());
452 void ServerList::saveColumnsWidthsSettings()
454 gConfig.doomseeker.serverListColumnState = table->horizontalHeader()->saveState().toBase64();
455 gConfig.doomseeker.serverListSortIndex = sortIndex;
456 gConfig.doomseeker.serverListSortDirection = sortOrder;
459 QList<ServerPtr> ServerList::selectedServers()
const
461 QModelIndexList indexList = table->selectionModel()->selectedRows();
463 QList<ServerPtr> servers;
464 for (
int i = 0; i < indexList.count(); ++i)
466 QModelIndex realIndex = proxyModel->mapToSource(indexList[i]);
467 ServerPtr server = model->serverFromList(realIndex);
468 servers.append(server);
473 void ServerList::onServerBegunRefreshing(
const ServerPtr &server)
475 model->setRefreshing(server);
478 QList<ServerPtr> ServerList::servers()
const
480 return model->servers();
483 ServerPtr ServerList::serverFromIndex(
const QModelIndex &index)
485 auto pModel =
static_cast<QSortFilterProxyModel *
>(table->model());
486 QModelIndex indexReal = pModel->mapToSource(index);
487 return model->serverFromList(indexReal);
490 QList<ServerPtr> ServerList::serversForPlugin(
const EnginePlugin *plugin)
const
492 return model->serversForPlugin(plugin);
495 void ServerList::onServerUpdated(
const ServerPtr &server)
503 needsCleaning =
true;
509 const bool FORCE =
true;
510 updateCountryFlags(!FORCE);
513 void ServerList::sortAdditionally(
const QModelIndex &modelIndex, Qt::SortOrder order)
516 model->addAdditionalColumnSorting(modelIndex.column(), order);
519 Qt::SortOrder ServerList::swappedCurrentSortOrder()
521 return sortOrder == Qt::AscendingOrder ? Qt::DescendingOrder : Qt::AscendingOrder;
524 void ServerList::tableMiddleClicked()
529 void ServerList::showContextMenu(
const QPoint &position)
531 const QModelIndex index = table->indexAt(position);
532 ServerPtr server = serverFromIndex(index);
534 if (server ==
nullptr)
538 proxyModel->filterInfo(), index, selectedServers(),
this);
539 this->connect(contextMenu, SIGNAL(aboutToHide()), SLOT(contextMenuAboutToHide()));
540 this->connect(contextMenu, SIGNAL(triggered(QAction*)), SLOT(contextMenuTriggered(QAction*)));
542 QPoint displayPoint = table->viewport()->mapToGlobal(position);
543 contextMenu->popup(displayPoint);
546 void ServerList::updateCountryFlags()
548 const bool FORCE =
true;
549 updateCountryFlags(FORCE);
552 void ServerList::updateCountryFlags(
bool force)
554 for (
int i = 0; i < model->rowCount(); ++i)
558 void ServerList::updateHeaderTitles()
560 const QList<ColumnSort> &sortings = proxyModel->additionalSortColumns();
561 for (
int i = 0; i < ServerListColumnId::NUM_SERVERLIST_COLUMNS; ++i)
564 model->setHeaderData(i, Qt::Horizontal, QIcon(), Qt::DecorationRole);
566 QStringList labels = ServerListColumns::generateColumnHeaderLabels();
567 for (
int i = 0; i < sortings.size(); ++i)
570 labels[sort.columnId()] = QString(
"[%1] %2").arg(i + 1).arg(labels[sort.columnId()]);
571 QIcon icon = sort.order() == Qt::AscendingOrder ?
572 QIcon(
":/icons/ascending.png") :
573 QIcon(
":/icons/descending.png");
574 model->setHeaderData(sort.columnId(), Qt::Horizontal, icon, Qt::DecorationRole);
576 model->setHorizontalHeaderLabels(labels);