serverlist.cpp
1 //------------------------------------------------------------------------------
2 // serverlist.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 // 02110-1301, USA.
19 //
20 //------------------------------------------------------------------------------
21 // Copyright (C) 2009 "Zalewa" <zalewapl@gmail.com>
22 //------------------------------------------------------------------------------
23 #include "serverlist.h"
24 
25 #include "configuration/doomseekerconfig.h"
26 #include "gui/remoteconsole.h"
27 #include "gui/models/serverlistcolumn.h"
28 #include "gui/models/serverlistmodel.h"
29 #include "gui/models/serverlistproxymodel.h"
30 #include "gui/widgets/serverlistcontextmenu.h"
31 #include "gui/widgets/serverlistview.h"
32 #include "refresher/refresher.h"
33 #include "serverapi/tooltips/servertooltip.h"
34 #include "serverapi/server.h"
35 #include "urlopener.h"
36 #include <QHeaderView>
37 #include <QMessageBox>
38 #include <QToolTip>
39 
40 using namespace ServerListColumnId;
41 
42 ServerList::ServerList(ServerListView* serverTable, QWidget* pMainWindow)
43 : mainWindow(pMainWindow), model(NULL),
44  proxyModel(NULL), sortOrder(Qt::AscendingOrder),
45  sortIndex(-1), table(serverTable)
46 {
47  prepareServerTable();
48 
49  needsCleaning = false;
50 
51  initCleanerTimer();
52 }
53 
54 ServerList::~ServerList()
55 {
56  saveColumnsWidthsSettings();
57 }
58 
59 void ServerList::applyFilter(const ServerListFilterInfo& filterInfo)
60 {
61  gConfig.serverFilter.info = filterInfo;
62  proxyModel->setFilterInfo(filterInfo);
63  needsCleaning = true;
64 }
65 
66 bool ServerList::areColumnsWidthsSettingsChanged()
67 {
68  for(int i = 0; i < NUM_SERVERLIST_COLUMNS; ++i)
69  {
70  if(ServerListColumns::columns[i].width != table->columnWidth(i))
71  {
72  return true;
73  }
74  }
75 
76  return false;
77 }
78 
79 void ServerList::cleanUp()
80 {
81  if (needsCleaning && mainWindow->isActiveWindow())
82  {
83  if (sortIndex >= 0)
84  {
85  ServerListProxyModel* pModel = static_cast<ServerListProxyModel*>(table->model());
86  pModel->invalidate();
87  pModel->sortServers(sortIndex, sortOrder);
88  }
89 
90  setCountryFlagsIfNotPresent();
91  needsCleaning = false;
92  }
93 }
94 
95 void ServerList::cleanUpForce()
96 {
97  needsCleaning = true;
98  cleanUp();
99 }
100 
101 void ServerList::clearAdditionalSorting()
102 {
103  proxyModel->clearAdditionalSorting();
104 }
105 
106 void ServerList::columnHeaderClicked(int index)
107 {
108  if (isSortingByColumn(index))
109  {
110  sortOrder = swappedCurrentSortOrder();
111  }
112  else
113  {
114  sortOrder = getColumnDefaultSortOrder(index);
115  }
116  sortIndex = index;
117 
118  cleanUpForce();
119 
120  QHeaderView* header = table->horizontalHeader();
121  header->setSortIndicator(sortIndex, sortOrder);
122 }
123 
124 void ServerList::connectTableModelProxySlots()
125 {
126  QHeaderView* header = table->horizontalHeader();
127  this->connect(header, SIGNAL(sectionClicked(int)), SLOT(columnHeaderClicked(int)));
128 
129  this->connect(table->selectionModel(),
130  SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
131  SLOT(itemSelected(QItemSelection)));
132  this->connect(table, SIGNAL(middleMouseClicked(QModelIndex, QPoint)),
133  SLOT(tableMiddleClicked(QModelIndex, QPoint)));
134  this->connect(table, SIGNAL(rightMouseClicked(QModelIndex, QPoint)),
135  SLOT(tableRightClicked(QModelIndex, QPoint)));
136  this->connect(table, SIGNAL(entered(QModelIndex)), SLOT(mouseEntered(QModelIndex)));
137  this->connect(table, SIGNAL(leftMouseDoubleClicked(QModelIndex, QPoint)),
138  SLOT(doubleClicked(QModelIndex)));
139 }
140 
141 void ServerList::contextMenuAboutToHide()
142 {
143  sender()->deleteLater();
144 }
145 
146 void ServerList::contextMenuTriggered(QAction* action)
147 {
148  ServerListContextMenu *contextMenu = static_cast<ServerListContextMenu*>(sender());
149  ServerPtr server = contextMenu->server();
150  // 1. This is a bit convoluted, but emitting the serverFilterModified
151  // signal leads to a call to applyFilter() in this class.
152  // 2. Since the menu modifies existing server filter, the worst that can
153  // happen is that we set the same filter again.
154  emit serverFilterModified(contextMenu->serverFilter());
155 
156  ServerListContextMenu::Result contextMenuResult = contextMenu->translateQMenuResult(action);
157  switch (contextMenuResult)
158  {
160  // Do nothing.
161  break;
162 
163  case ServerListContextMenu::FindMissingWADs:
164  emit findMissingWADs(server);
165  break;
166 
167  case ServerListContextMenu::Join:
168  emit serverDoubleClicked(server);
169  break;
170 
171  case ServerListContextMenu::OpenRemoteConsole:
172  new RemoteConsole(server);
173  break;
174 
175  case ServerListContextMenu::OpenURL:
176  // Calling QDesktopServices::openUrl() here directly resulted
177  // in a crash somewhere in Qt libraries. UrlOpener defers the
178  // call with a timer and this fixes the crash.
179  UrlOpener::instance()->open(server->webSite());
180  break;
181 
183  // Do nothing; ignore.
184  break;
185 
186  case ServerListContextMenu::Refresh:
187  refreshSelected();
188  break;
189 
190  case ServerListContextMenu::ShowJoinCommandLine:
191  emit displayServerJoinCommandLine(server);
192  break;
193 
194  case ServerListContextMenu::SortAdditionallyAscending:
195  sortAdditionally(contextMenu->modelIndex(), Qt::AscendingOrder);
196  break;
197 
198  case ServerListContextMenu::SortAdditionallyDescending:
199  sortAdditionally(contextMenu->modelIndex(), Qt::DescendingOrder);
200  break;
201 
202  case ServerListContextMenu::RemoveAdditionalSortingForColumn:
203  removeAdditionalSortingForColumn(contextMenu->modelIndex());
204  break;
205 
206  case ServerListContextMenu::ClearAdditionalSorting:
207  clearAdditionalSorting();
208  break;
209 
210  default:
211  QMessageBox::warning(mainWindow, tr("Doomseeker - context menu warning"),
212  tr("Unhandled behavior in ServerList::contextMenuTriggered()"));
213  break;
214  }
215 }
216 
217 ServerListModel* ServerList::createModel()
218 {
219  ServerListModel* serverListModel = new ServerListModel(this);
220  serverListModel->prepareHeaders();
221  return serverListModel;
222 }
223 
224 ServerListProxyModel *ServerList::createSortingProxy(ServerListModel* serverListModel)
225 {
226  ServerListProxyModel* proxy = new ServerListProxyModel(this);
227  this->connect(proxy, SIGNAL(additionalSortColumnsChanged()),
228  SLOT(updateHeaderTitles()));
229  this->connect(proxy, SIGNAL(additionalSortColumnsChanged()),
230  SLOT(saveAdditionalSortingConfig()));
231  proxy->setSourceModel(serverListModel);
232  proxy->setSortRole(ServerListModel::SLDT_SORT);
233  proxy->setSortCaseSensitivity(Qt::CaseInsensitive);
234  proxy->setFilterKeyColumn(IDServerName);
235 
236  return proxy;
237 }
238 
239 void ServerList::doubleClicked(const QModelIndex& index)
240 {
241  emit serverDoubleClicked(serverFromIndex(index));
242 }
243 
244 Qt::SortOrder ServerList::getColumnDefaultSortOrder(int columnId)
245 {
246  // Right now we can assume that columnIndex == columnId.
247  return ServerListColumns::columns[columnId].defaultSortOrder;
248 }
249 
250 bool ServerList::hasAtLeastOneServer() const
251 {
252  return model->rowCount() > 0;
253 }
254 
255 void ServerList::initCleanerTimer()
256 {
257  cleanerTimer.setInterval(200);
258  cleanerTimer.start();
259  connect(&cleanerTimer, SIGNAL( timeout() ), this, SLOT ( cleanUp() ) );
260 }
261 
262 bool ServerList::isAnyColumnSortedAdditionally() const
263 {
264  return proxyModel->isAnyColumnSortedAdditionally();
265 }
266 
267 bool ServerList::isSortingAdditionallyByColumn(int column) const
268 {
269  return proxyModel->isSortingAdditionallyByColumn(column);
270 }
271 
272 bool ServerList::isSortingByColumn(int columnIndex)
273 {
274  return sortIndex == columnIndex;
275 }
276 
277 void ServerList::itemSelected(const QItemSelection& selection)
278 {
279  QSortFilterProxyModel* pModel = static_cast<QSortFilterProxyModel*>(table->model());
280  QModelIndexList indexList = selection.indexes();
281 
282  QList<ServerPtr> servers;
283  for(int i = 0; i < indexList.count(); ++i)
284  {
285  QModelIndex realIndex = pModel->mapToSource(indexList[i]);
286  ServerPtr server = model->serverFromList(realIndex);
287  servers.append(server);
288  }
289  emit serversSelected(servers);
290 }
291 
293 {
294  for (int i = 0; i < model->rowCount(); ++i)
295  {
296  ServerPtr server = model->serverFromList(i);
297  server->lookupHost();
298  }
299 }
300 
301 void ServerList::mouseEntered(const QModelIndex& index)
302 {
303  QSortFilterProxyModel* pModel = static_cast<QSortFilterProxyModel*>(table->model());
304  QModelIndex realIndex = pModel->mapToSource(index);
305  ServerPtr server = model->serverFromList(realIndex);
306  QString tooltip;
307 
308  // Functions inside cases perform checks on the server structure
309  // to see if any tooltip should be generated. Empty string is returned
310  // in case if it should be not.
311  switch(index.column())
312  {
313  case IDPort:
314  tooltip = ServerTooltip::createPortToolTip(server);
315  break;
316 
317  case IDAddress:
318  tooltip = server->hostName(true);
319  break;
320 
321  case IDPlayers:
322  tooltip = ServerTooltip::createPlayersToolTip(server);
323  break;
324 
325  case IDServerName:
326  tooltip = ServerTooltip::createServerNameToolTip(server);
327  break;
328 
329  case IDIwad:
330  tooltip = ServerTooltip::createIwadToolTip(server);
331  break;
332 
333  case IDWads:
334  tooltip = ServerTooltip::createPwadsToolTip(server);
335  break;
336 
337  default:
338  tooltip = "";
339  break;
340  }
341 
342  QToolTip::showText(QCursor::pos(), tooltip, NULL);
343 }
344 
345 void ServerList::prepareServerTable()
346 {
347  model = createModel();
348  proxyModel = createSortingProxy(model);
349 
350  columnHeaderClicked(IDPlayers);
351  table->setModel(proxyModel);
352  table->setupTableProperties();
353 
354  if(gConfig.doomseeker.serverListSortIndex >= 0)
355  {
356  sortIndex = gConfig.doomseeker.serverListSortIndex;
357  sortOrder = static_cast<Qt::SortOrder> (gConfig.doomseeker.serverListSortDirection);
358  }
359 
360  connectTableModelProxySlots();
361  proxyModel->setAdditionalSortColumns(gConfig.doomseeker.additionalSortColumns());
362 }
363 
364 void ServerList::redraw()
365 {
366  model->redrawAll();
367 }
368 
369 void ServerList::refreshSelected()
370 {
371  QItemSelectionModel* selectionModel = table->selectionModel();
372  QModelIndexList indexList = selectionModel->selectedRows();
373 
374  for(int i = 0; i < indexList.count(); ++i)
375  {
376  QModelIndex realIndex = proxyModel->mapToSource(indexList[i]);
377  gRefresher->registerServer(model->serverFromList(realIndex).data());
378  }
379 }
380 
381 void ServerList::registerServer(ServerPtr server)
382 {
383  this->connect(server.data(), SIGNAL(updated(ServerPtr, int)),
384  SLOT(onServerUpdated(ServerPtr)));
385  this->connect(server.data(), SIGNAL(begunRefreshing(ServerPtr)),
386  SLOT(onServerBegunRefreshing(ServerPtr)));
387  model->addServer(server);
388  emit serverRegistered(server);
389 }
390 
391 void ServerList::removeServer(const ServerPtr &server)
392 {
393  server->disconnect(this);
394  model->removeServer(server);
395  emit serverDeregistered(server);
396 }
397 
398 void ServerList::removeCustomServers()
399 {
400  foreach (ServerPtr server, model->customServers())
401  {
402  removeServer(server);
403  }
404 }
405 
406 void ServerList::removeNonSpecialServers()
407 {
408  foreach (ServerPtr server, model->nonSpecialServers())
409  {
410  removeServer(server);
411  }
412 }
413 
414 void ServerList::removeAdditionalSortingForColumn(const QModelIndex &modelIndex)
415 {
416  proxyModel->removeAdditionalColumnSorting(modelIndex.column());
417 }
418 
419 void ServerList::saveAdditionalSortingConfig()
420 {
421  gConfig.doomseeker.setAdditionalSortColumns(proxyModel->additionalSortColumns());
422 }
423 
424 void ServerList::saveColumnsWidthsSettings()
425 {
426  gConfig.doomseeker.serverListColumnState = table->horizontalHeader()->saveState().toBase64();
427  gConfig.doomseeker.serverListSortIndex = sortIndex;
428  gConfig.doomseeker.serverListSortDirection = sortOrder;
429 }
430 
431 QList<ServerPtr> ServerList::selectedServers() const
432 {
433  QModelIndexList indexList = table->selectionModel()->selectedRows();
434 
435  QList<ServerPtr> servers;
436  for(int i = 0; i < indexList.count(); ++i)
437  {
438  QModelIndex realIndex = proxyModel->mapToSource(indexList[i]);
439  ServerPtr server = model->serverFromList(realIndex);
440  servers.append(server);
441  }
442  return servers;
443 }
444 
445 void ServerList::onServerBegunRefreshing(const ServerPtr &server)
446 {
447  model->setRefreshing(server);
448 }
449 
450 QList<ServerPtr> ServerList::servers() const
451 {
452  return model->servers();
453 }
454 
455 ServerPtr ServerList::serverFromIndex(const QModelIndex &index)
456 {
457  QSortFilterProxyModel* pModel = static_cast<QSortFilterProxyModel*>(table->model());
458  QModelIndex indexReal = pModel->mapToSource(index);
459  return model->serverFromList(indexReal);
460 }
461 
462 QList<ServerPtr> ServerList::serversForPlugin(const EnginePlugin *plugin) const
463 {
464  return model->serversForPlugin(plugin);
465 }
466 
467 void ServerList::onServerUpdated(const ServerPtr &server)
468 {
469  int rowIndex = model->findServerOnTheList(server.data());
470  if (rowIndex >= 0)
471  {
472  rowIndex = model->updateServer(rowIndex, server);
473  }
474  else
475  {
476  rowIndex = model->addServer(server);
477  }
478 
479  needsCleaning = true;
480  emit serverInfoUpdated(server);
481 }
482 
484 {
485  const bool FORCE = true;
486  updateCountryFlags(!FORCE);
487 }
488 
489 void ServerList::setGroupServersWithPlayersAtTop(bool b)
490 {
491  proxyModel->setGroupServersWithPlayersAtTop(b);
492 }
493 
494 void ServerList::sortAdditionally(const QModelIndex &modelIndex, Qt::SortOrder order)
495 {
496  ServerListProxyModel* model = static_cast<ServerListProxyModel*>(table->model());
497  model->addAdditionalColumnSorting(modelIndex.column(), order);
498 }
499 
500 Qt::SortOrder ServerList::swappedCurrentSortOrder()
501 {
502  return sortOrder == Qt::AscendingOrder ? Qt::DescendingOrder : Qt::AscendingOrder;
503 }
504 
505 void ServerList::tableMiddleClicked(const QModelIndex& index, const QPoint& cursorPosition)
506 {
507  refreshSelected();
508 }
509 
510 void ServerList::tableRightClicked(const QModelIndex& index, const QPoint& cursorPosition)
511 {
512  ServerPtr server = serverFromIndex(index);
513 
514  ServerListContextMenu *contextMenu = new ServerListContextMenu(server,
515  proxyModel->filterInfo(), index, this);
516  this->connect(contextMenu, SIGNAL(aboutToHide()), SLOT(contextMenuAboutToHide()));
517  this->connect(contextMenu, SIGNAL(triggered(QAction*)), SLOT(contextMenuTriggered(QAction*)));
518 
519  QPoint displayPoint = table->viewport()->mapToGlobal(cursorPosition);
520  contextMenu->popup(displayPoint);
521 }
522 
523 void ServerList::updateCountryFlags()
524 {
525  const bool FORCE = true;
526  updateCountryFlags(FORCE);
527 }
528 
529 void ServerList::updateCountryFlags(bool force)
530 {
531  for (int i = 0; i < model->rowCount(); ++i)
532  {
533  model->updateFlag(i, force);
534  }
535 }
536 
537 void ServerList::updateHeaderTitles()
538 {
539  const QList<ColumnSort> &sortings = proxyModel->additionalSortColumns();
540  for (int i = 0; i < ServerListColumnId::NUM_SERVERLIST_COLUMNS; ++i)
541  {
542  // Clear header icons.
543  model->setHeaderData(i, Qt::Horizontal, QIcon(), Qt::DecorationRole);
544  }
545  QStringList labels = ServerListColumns::generateColumnHeaderLabels();
546  for (int i = 0; i < sortings.size(); ++i)
547  {
548  const ColumnSort &sort = sortings[i];
549  labels[sort.columnId()] = QString("[%1] %2").arg(i + 1).arg(labels[sort.columnId()]);
550  QIcon icon = sort.order() == Qt::AscendingOrder ?
551  QIcon(":/icons/ascending.png") :
552  QIcon(":/icons/descending.png");
553  model->setHeaderData(sort.columnId(), Qt::Horizontal, icon, Qt::DecorationRole);
554  }
555  model->setHorizontalHeaderLabels(labels);
556 }
557 
558 void ServerList::updateSearch(const QString& search)
559 {
560  QRegExp pattern(QString("*") + search + "*", Qt::CaseInsensitive, QRegExp::Wildcard);
561  proxyModel->setFilterRegExp(pattern);
562 }
Structure describing server filter.
void lookupHosts()
Looks up hosts for all available servers.
Definition: serverlist.cpp:292
This is returned when something was copied to clipboard.
void setCountryFlagsIfNotPresent()
Sets country flags for servers that don&#39;t have flags present yet.
Definition: serverlist.cpp:483