23 #include "configuration/doomseekerconfig.h"
24 #include "gui/remoteconsole.h"
25 #include "gui/serverlist.h"
26 #include "gui/models/serverlistcolumn.h"
27 #include "gui/models/serverlistmodel.h"
28 #include "gui/models/serverlistproxymodel.h"
29 #include "gui/models/serverlistrowhandler.h"
30 #include "gui/widgets/serverlistcontextmenu.h"
31 #include "gui/widgets/serverlistview.h"
32 #include "pathfinder/pathfinder.h"
33 #include "pathfinder/wadpathfinder.h"
34 #include "plugins/engineplugin.h"
35 #include "refresher/refresher.h"
36 #include "serverapi/tooltips/tooltipgenerator.h"
37 #include "serverapi/gameexeretriever.h"
38 #include "serverapi/message.h"
39 #include "serverapi/playerslist.h"
40 #include "serverapi/server.h"
42 #include "urlopener.h"
43 #include <QApplication>
45 #include <QDesktopServices>
46 #include <QHeaderView>
48 #include <QMessageBox>
49 #include <QStandardItem>
53 const QString ServerListHandler::FONT_COLOR_MISSING =
"#ff0000";
54 const QString ServerListHandler::FONT_COLOR_OPTIONAL =
"#ff9f00";
55 const QString ServerListHandler::FONT_COLOR_FOUND =
"#009f00";
57 using namespace ServerListColumnId;
59 ServerListHandler::ServerListHandler(
ServerListView* serverTable, QWidget* pMainWindow)
60 : mainWindow(pMainWindow), model(NULL),
61 sortingProxy(NULL), sortOrder(Qt::AscendingOrder),
62 sortIndex(-1), table(serverTable)
66 needsCleaning =
false;
71 ServerListHandler::~ServerListHandler()
73 saveColumnsWidthsSettings();
78 gConfig.serverFilter.info = filterInfo;
86 bool ServerListHandler::areColumnsWidthsSettingsChanged()
88 for(
int i = 0; i < NUM_SERVERLIST_COLUMNS; ++i)
90 if(ServerListColumns::columns[i].width != table->columnWidth(i))
99 void ServerListHandler::clearTable()
101 model->destroyRows();
104 void ServerListHandler::cleanUp()
106 if (needsCleaning && mainWindow->isActiveWindow())
111 pModel->invalidate();
112 pModel->sortServers(sortIndex, sortOrder);
116 table->updateAllRows();
117 needsCleaning =
false;
121 void ServerListHandler::cleanUpForce()
123 needsCleaning =
true;
127 void ServerListHandler::clearAdditionalSorting()
129 sortingProxy->clearAdditionalSorting();
134 if (isSortingByColumn(index))
136 sortOrder = swapCurrentSortOrder();
140 sortOrder = getColumnDefaultSortOrder(index);
146 QHeaderView* header = table->horizontalHeader();
147 header->setSortIndicator(sortIndex, sortOrder);
150 void ServerListHandler::connectTableModelProxySlots()
152 QHeaderView* header = table->horizontalHeader();
154 connect(model, SIGNAL( modelCleared() ),
this, SLOT( modelCleared() ) );
155 connect(table->selectionModel(), SIGNAL( selectionChanged(
const QItemSelection&,
const QItemSelection&) ),
this, SLOT( itemSelected(
const QItemSelection&) ));
156 connect(table, SIGNAL( middleMouseClick(
const QModelIndex&,
const QPoint&) ),
this, SLOT( tableMiddleClicked(
const QModelIndex&,
const QPoint&) ) );
157 connect(table, SIGNAL( rightMouseClick(
const QModelIndex&,
const QPoint&) ),
this, SLOT ( tableRightClicked(
const QModelIndex&,
const QPoint&)) );
158 connect(table, SIGNAL( entered(
const QModelIndex&) ),
this, SLOT ( mouseEntered(
const QModelIndex&)) );
159 connect(table, SIGNAL( leftMouseDoubleClicked(
const QModelIndex&,
const QPoint&)),
this, SLOT( doubleClicked(
const QModelIndex&)) );
162 void ServerListHandler::contextMenuAboutToHide()
164 sender()->deleteLater();
167 void ServerListHandler::contextMenuTriggered(QAction* action)
170 ServerPtr server = contextMenu->server();
175 emit serverFilterModified(contextMenu->serverFilter());
178 switch (contextMenuResult)
184 case ServerListContextMenu::FindMissingWADs:
188 case ServerListContextMenu::Join:
189 emit serverDoubleClicked(server);
192 case ServerListContextMenu::OpenRemoteConsole:
196 case ServerListContextMenu::OpenURL:
200 UrlOpener::instance()->open(server->webSite());
207 case ServerListContextMenu::Refresh:
211 case ServerListContextMenu::ShowJoinCommandLine:
215 case ServerListContextMenu::SortAdditionallyAscending:
216 sortAdditionally(contextMenu->modelIndex(), Qt::AscendingOrder);
219 case ServerListContextMenu::SortAdditionallyDescending:
220 sortAdditionally(contextMenu->modelIndex(), Qt::DescendingOrder);
223 case ServerListContextMenu::RemoveAdditionalSortingForColumn:
224 removeAdditionalSortingForColumn(contextMenu->modelIndex());
227 case ServerListContextMenu::ClearAdditionalSorting:
228 clearAdditionalSorting();
232 QMessageBox::warning(mainWindow, tr(
"Doomseeker - context menu warning"),
233 tr(
"Unhandled behavior in ServerListHandler::contextMenuTriggered()"));
238 QString ServerListHandler::createIwadToolTip(ServerPtr server)
240 if (!server->isKnown())
246 bool bFindIwad = gConfig.doomseeker.bTellMeWhereAreTheWADsWhenIHoverCursorOverWADSColumn;
250 static const QString FORMAT_TEMPLATE =
"<font color=\"%1\">%2</font>";
254 QString binPath =
GameExeRetriever(*server->plugin()->gameExe()).pathToOfflineExe(binMessage);
260 QString msg = path.path();
263 msg +=
" " + tr(
"(alias of: %1)").arg(server->iwad());
265 return FORMAT_TEMPLATE.arg(FONT_COLOR_FOUND, msg);
269 return FORMAT_TEMPLATE.arg(FONT_COLOR_MISSING, tr(
"MISSING"));
279 serverListModel->prepareHeaders();
281 return serverListModel;
284 QString ServerListHandler::createPlayersToolTip(ServerCPtr server)
286 if (server == NULL || !server->isKnown())
294 ret =
"<div style='white-space: pre'>";
296 if(server->players().numClients() != 0)
302 delete tooltipGenerator;
306 QString ServerListHandler::createPortToolTip(ServerCPtr server)
308 if (server == NULL || !server->isKnown())
312 if (server->isLocked())
313 ret +=
"Password protected\n";
314 if (server->isLockedInGame())
315 ret +=
"Password protected in-game\n";
316 if (server->isSecure())
317 ret +=
"Enforces master bans\n";
318 return ret.trimmed();
321 QString ServerListHandler::createPwadsToolTip(ServerPtr server)
323 if (server == NULL || !server->isKnown() || server->numWads() == 0)
329 static const QString toolTip =
"<div style='white-space: pre'>%1</div>";
332 const QList<PWad>& pwads = server->wads();
335 bool bFindWads = gConfig.doomseeker.bTellMeWhereAreTheWADsWhenIHoverCursorOverWADSColumn;
340 QStringList pwadsFormatted;
341 foreach (
const PWad &wad, pwads)
343 pwadsFormatted << createPwadToolTipInfo(wad, server);
346 content =
"<table cellspacing=1>";
347 content += pwadsFormatted.join(
"\n");
348 content +=
"</table>";
352 foreach (
const PWad &wad, pwads)
354 content += wad.
name() +
"\n";
359 return toolTip.arg(content);
362 QString ServerListHandler::createPwadToolTipInfo(
const PWad& pwad,
const ServerPtr &server)
366 QString fontColor =
"#777777";
369 cells << pwad.
name();
372 fontColor = FONT_COLOR_FOUND;
373 cells << path.path();
379 fontColor = FONT_COLOR_OPTIONAL;
380 cells << tr(
"OPTIONAL");
384 fontColor = FONT_COLOR_MISSING;
385 cells << tr(
"MISSING");
390 cells << tr(
"ALIAS");
397 QString formattedStringBegin = QString(
"<tr style=\"color: %1;\">").arg(fontColor);
398 QString formattedStringMiddle;
400 foreach (
const QString &cell, cells)
402 formattedStringMiddle += QString(
"<td style=\"padding-right: 5;\">%1</td>").arg(cell);
405 return formattedStringBegin + formattedStringMiddle +
"</tr>";
408 QString ServerListHandler::createServerNameToolTip(ServerCPtr server)
420 if (!generalInfo.isEmpty())
422 ret =
"<div style='white-space: pre'>";
427 delete tooltipGenerator;
434 this->connect(proxy, SIGNAL(additionalSortColumnsChanged()),
435 SLOT(updateHeaderTitles()));
436 this->connect(proxy, SIGNAL(additionalSortColumnsChanged()),
437 SLOT(saveAdditionalSortingConfig()));
438 proxy->setSourceModel(serverListModel);
439 proxy->setSortRole(ServerListModel::SLDT_SORT);
440 proxy->setSortCaseSensitivity( Qt::CaseInsensitive );
441 proxy->setFilterKeyColumn(IDServerName);
446 void ServerListHandler::doubleClicked(
const QModelIndex& index)
448 emit serverDoubleClicked(serverFromIndex(index));
451 WadFindResult ServerListHandler::findWad(ServerPtr server,
const QString &name)
const
453 PathFinder pathFinder = server->wadPathFinder();
455 return wadFinder.find(name);
458 Qt::SortOrder ServerListHandler::getColumnDefaultSortOrder(
int columnId)
461 return ServerListColumns::columns[columnId].defaultSortOrder;
464 bool ServerListHandler::hasAtLeastOneServer()
const
466 return model->rowCount() > 0;
469 void ServerListHandler::initCleanerTimer()
471 cleanerTimer.setInterval(200);
472 cleanerTimer.start();
473 connect(&cleanerTimer, SIGNAL( timeout() ),
this, SLOT ( cleanUp() ) );
476 bool ServerListHandler::isAnyColumnSortedAdditionally()
const
478 return sortingProxy->isAnyColumnSortedAdditionally();
481 bool ServerListHandler::isSortingAdditionallyByColumn(
int column)
const
483 return sortingProxy->isSortingAdditionallyByColumn(column);
486 bool ServerListHandler::isSortingByColumn(
int columnIndex)
488 return sortIndex == columnIndex;
491 void ServerListHandler::itemSelected(
const QItemSelection& selection)
493 QSortFilterProxyModel* pModel =
static_cast<QSortFilterProxyModel*
>(table->model());
494 QModelIndexList indexList = selection.indexes();
496 QList<ServerPtr> servers;
497 for(
int i = 0; i < indexList.count(); ++i)
499 QModelIndex realIndex = pModel->mapToSource(indexList[i]);
500 ServerPtr server = model->serverFromList(realIndex);
501 servers.append(server);
503 emit serversSelected(servers);
508 for (
int i = 0; i < model->rowCount(); ++i)
510 ServerPtr server = model->serverFromList(i);
511 server->lookupHost();
515 void ServerListHandler::modelCleared()
517 QList<ServerPtr> servers;
518 emit serversSelected(servers);
521 void ServerListHandler::mouseEntered(
const QModelIndex& index)
523 QSortFilterProxyModel* pModel =
static_cast<QSortFilterProxyModel*
>(table->model());
524 QModelIndex realIndex = pModel->mapToSource(index);
525 ServerPtr server = model->serverFromList(realIndex);
531 switch(index.column())
534 tooltip = createPortToolTip(server);
538 tooltip = server->hostName(
true);
542 tooltip = createPlayersToolTip(server);
546 tooltip = createServerNameToolTip(server);
550 tooltip = createIwadToolTip(server);
554 tooltip = createPwadsToolTip(server);
562 QToolTip::showText(QCursor::pos(), tooltip, table);
565 void ServerListHandler::prepareServerTable()
567 model = createModel();
568 sortingProxy = createSortingProxy(model);
571 setupTableProperties(sortingProxy);
573 connectTableModelProxySlots();
574 sortingProxy->setAdditionalSortColumns(gConfig.doomseeker.additionalSortColumns());
577 void ServerListHandler::redraw()
582 void ServerListHandler::refreshAll()
584 for (
int i = 0; i < model->rowCount(); ++i)
586 gRefresher->registerServer(model->serverFromList(i).data());
590 void ServerListHandler::refreshSelected()
592 QItemSelectionModel* selectionModel = table->selectionModel();
593 QModelIndexList indexList = selectionModel->selectedRows();
595 for(
int i = 0; i < indexList.count(); ++i)
597 QModelIndex realIndex = sortingProxy->mapToSource(indexList[i]);
598 gRefresher->registerServer(model->serverFromList(realIndex).data());
602 void ServerListHandler::removeAdditionalSortingForColumn(
const QModelIndex &modelIndex)
604 sortingProxy->removeAdditionalColumnSorting(modelIndex.column());
607 void ServerListHandler::saveAdditionalSortingConfig()
609 gConfig.doomseeker.setAdditionalSortColumns(sortingProxy->additionalSortColumns());
612 void ServerListHandler::saveColumnsWidthsSettings()
614 gConfig.doomseeker.serverListColumnState = table->horizontalHeader()->saveState().toBase64();
615 gConfig.doomseeker.serverListSortIndex = sortIndex;
616 gConfig.doomseeker.serverListSortDirection = sortOrder;
619 QList<ServerPtr> ServerListHandler::selectedServers()
621 QSortFilterProxyModel* pModel =
static_cast<QSortFilterProxyModel*
>(table->model());
622 QItemSelectionModel* selModel = table->selectionModel();
623 QModelIndexList indexList = selModel->selectedRows();
625 QList<ServerPtr> servers;
626 for(
int i = 0; i < indexList.count(); ++i)
628 QModelIndex realIndex = pModel->mapToSource(indexList[i]);
629 ServerPtr server = model->serverFromList(realIndex);
630 servers.append(server);
635 void ServerListHandler::serverBegunRefreshing(
const ServerPtr &server)
637 model->setRefreshing(server);
640 ServerPtr ServerListHandler::serverFromIndex(
const QModelIndex &index)
642 QSortFilterProxyModel* pModel =
static_cast<QSortFilterProxyModel*
>(table->model());
643 QModelIndex indexReal = pModel->mapToSource(index);
644 return model->serverFromList(indexReal);
647 void ServerListHandler::serverUpdated(
const ServerPtr &server,
int response)
652 rowIndex = model->
updateServer(rowIndex, server, response);
656 rowIndex = model->
addServer(server, response);
659 needsCleaning =
true;
665 const bool FORCE =
true;
666 updateCountryFlags(!FORCE);
669 void ServerListHandler::setGroupServersWithPlayersAtTop(
bool b)
671 sortingProxy->setGroupServersWithPlayersAtTop(b);
674 void ServerListHandler::setupTableColumnWidths()
676 QString &headerState = gConfig.doomseeker.serverListColumnState;
677 if(headerState.isEmpty())
679 for (
int i = 0; i < NUM_SERVERLIST_COLUMNS; ++i)
682 table->setColumnWidth(i, columns[i].width);
683 table->setColumnHidden(i, columns[i].bHidden);
684 if(!columns[i].bResizable)
686 table->horizontalHeader()->setResizeMode(i, QHeaderView::Fixed);
691 table->horizontalHeader()->restoreState(QByteArray::fromBase64(headerState.toAscii()));
693 table->horizontalHeader()->setMovable(
true);
695 if(gConfig.doomseeker.serverListSortIndex >= 0)
697 sortIndex = gConfig.doomseeker.serverListSortIndex;
698 sortOrder =
static_cast<Qt::SortOrder
> (gConfig.doomseeker.serverListSortDirection);
702 void ServerListHandler::setupTableProperties(QSortFilterProxyModel* tableModel)
704 table->setModel(tableModel);
705 table->setIconSize(QSize(26, 15));
707 table->verticalHeader()->hide();
709 table->horizontalHeader()->setSortIndicatorShown(
true);
710 table->horizontalHeader()->setHighlightSections(
false);
712 table->setMouseTracking(
true);
714 setupTableColumnWidths();
717 void ServerListHandler::sortAdditionally(
const QModelIndex &modelIndex, Qt::SortOrder order)
720 model->addAdditionalColumnSorting(modelIndex.column(), order);
723 Qt::SortOrder ServerListHandler::swapCurrentSortOrder()
725 if (sortOrder == Qt::AscendingOrder)
727 return Qt::DescendingOrder;
731 return Qt::AscendingOrder;
735 void ServerListHandler::tableMiddleClicked(
const QModelIndex& index,
const QPoint& cursorPosition)
740 void ServerListHandler::tableRightClicked(
const QModelIndex& index,
const QPoint& cursorPosition)
742 ServerPtr server = serverFromIndex(index);
746 pModel->filterInfo(), index,
this);
747 this->connect(contextMenu, SIGNAL(aboutToHide()), SLOT(contextMenuAboutToHide()));
748 this->connect(contextMenu, SIGNAL(triggered(QAction*)), SLOT(contextMenuTriggered(QAction*)));
750 QPoint displayPoint = table->viewport()->mapToGlobal(cursorPosition);
751 contextMenu->popup(displayPoint);
754 void ServerListHandler::updateCountryFlags()
756 const bool FORCE =
true;
757 updateCountryFlags(FORCE);
760 void ServerListHandler::updateCountryFlags(
bool force)
762 for (
int i = 0; i < model->rowCount(); ++i)
764 model->updateFlag(i, force);
768 void ServerListHandler::updateHeaderTitles()
770 const QList<ColumnSort> &sortings = sortingProxy->additionalSortColumns();
772 ServerListColumns::generateColumnHeaderLabels(labels);
773 for (
int i = 0; i < ServerListColumnId::NUM_SERVERLIST_COLUMNS; ++i)
776 model->setHeaderData(i, Qt::Horizontal, QIcon(), Qt::DecorationRole);
778 for (
int i = 0; i < sortings.size(); ++i)
781 labels[sort.columnId()] = QString(
"[%1] %2").arg(i + 1).arg(labels[sort.columnId()]);
782 QIcon icon = sort.order() == Qt::AscendingOrder ?
783 QIcon(
":/icons/ascending.png") :
784 QIcon(
":/icons/descending.png");
785 model->setHeaderData(sort.columnId(), Qt::Horizontal, icon, Qt::DecorationRole);
787 model->setHorizontalHeaderLabels(labels);
790 void ServerListHandler::updateSearch(
const QString& search)
792 QRegExp pattern(QString(
"*") + search +
"*", Qt::CaseInsensitive, QRegExp::Wildcard);
793 sortingProxy->setFilterRegExp(pattern);
Structure describing server filter.
void setCountryFlagsIfNotPresent()
Sets country flags for servers that don't have flags present yet.
Performs a case-insensitive (OS independent) file searches.
Message object used to pass messages throughout the Doomseeker's system.
A convenience wrapper class for GameExeFactory.
void findMissingWADs(const ServerPtr &)
void displayServerJoinCommandLine(const ServerPtr &)
void columnHeaderClicked(int)
Handles column sorting.
void lookupHosts()
Looks up hosts for all available servers.
int updateServer(int row, ServerPtr server, int response)
int addServer(ServerPtr server, int response)
int findServerOnTheList(const Server *server)
Finds index of the row where server is contained.
bool isOptional() const
Is this WAD required to join the server?
Wrapper for PathFinder that specializes in findings WADs.
void setFilterInfo(const ServerListFilterInfo &filterInfo)
Sets new filter info and immediately calls invalidate()
void serverInfoUpdated(const ServerPtr &)
Emitted every time when a server info is updated through serverUpdated()
const QString & name() const
File name of the WAD.