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