serverlistproxymodel.cpp
1 //------------------------------------------------------------------------------
2 // serverlistproxymodel.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) 2011 "Zalewa" <zalewapl@gmail.com>
22 //------------------------------------------------------------------------------
23 #include "serverlistproxymodel.h"
24 
25 #include "gui/entity/serverlistfilterinfo.h"
26 #include "gui/mainwindow.h"
27 #include "gui/models/serverlistcolumn.h"
28 #include "gui/models/serverlistmodel.h"
29 #include "gui/serverlist.h"
30 #include "serverapi/playerslist.h"
31 #include "serverapi/server.h"
33 
34 #include <QWidget>
35 
36 DClass<ServerListProxyModel>
37 {
38 public:
39  QList<ColumnSort> additionalSortColumns;
40  int mainSortColumn;
41  ServerList *parentHandler;
42  ServerListFilterInfo filterInfo;
43  Qt::SortOrder sortOrder;
44 
45  ColumnSort additionalSortForColumn(int column) const
46  {
47  for (const ColumnSort &sort : additionalSortColumns)
48  {
49  if (sort.columnId() == column)
50  return sort;
51  }
52  return ColumnSort();
53  }
54 
55  bool removeAdditionalColumnSorting(int column)
56  {
57  ColumnSort sort = additionalSortForColumn(column);
58  if (sort.isValid())
59  {
60  additionalSortColumns.removeAll(sort);
61  return true;
62  }
63  return false;
64  }
65 };
66 
67 DPointered(ServerListProxyModel)
68 
70  : QSortFilterProxyModel(serverListHandler)
71 {
72  d->mainSortColumn = -1;
73  d->parentHandler = serverListHandler;
74 }
75 
76 ServerListProxyModel::~ServerListProxyModel()
77 {
78 }
79 
80 void ServerListProxyModel::addAdditionalColumnSorting(int column, Qt::SortOrder order)
81 {
82  if (d->mainSortColumn == column)
83  {
84  // No-op.
85  return;
86  }
87  if (d->mainSortColumn >= 0)
88  {
89  d->removeAdditionalColumnSorting(column);
90  d->additionalSortColumns << ColumnSort(column, order);
91  emit additionalSortColumnsChanged();
92  }
93  else
94  {
95  d->mainSortColumn = column;
96  d->sortOrder = order;
97  }
98  sort(d->mainSortColumn, d->sortOrder);
99 }
100 
101 const QList<ColumnSort> &ServerListProxyModel::additionalSortColumns() const
102 {
103  return d->additionalSortColumns;
104 }
105 
106 void ServerListProxyModel::clearAdditionalSorting()
107 {
108  if (!d->additionalSortColumns.isEmpty())
109  {
110  d->additionalSortColumns.clear();
111  emit additionalSortColumnsChanged();
112  }
113 }
114 
115 #define RET_COMPARE(a, b) \
116  { \
117  if ((a) < (b)) \
118  return -1; \
119  if ((a) == (b)) \
120  return 0; \
121  else \
122  return 1; \
123  }
124 
125 int ServerListProxyModel::compareColumnSortData(QVariant &var1, QVariant &var2, int column) const
126 {
127  using namespace ServerListColumnId;
128 
129  if (!(var1.isValid() || !var2.isValid()))
130  {
131  if (var1.isValid())
132  return -1;
133  if (var2.isValid())
134  return 1;
135  return 0;
136  }
137 
138  switch (column)
139  {
140  case IDAddress:
141  RET_COMPARE(var1.toUInt(), var2.toUInt());
142 
143  case IDPing:
144  case IDPlayers:
145  RET_COMPARE(var1.toInt(), var2.toInt());
146 
147  case IDPort:
148  case IDGametype:
149  case IDIwad:
150  case IDMap:
151  case IDServerName:
152  case IDWads:
153  RET_COMPARE(var1.toString(), var2.toString());
154 
155  default:
156  return 0;
157  }
158 }
159 
160 bool ServerListProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
161 {
162  Q_UNUSED(sourceParent);
163  ServerPtr s = serverFromList(sourceRow);
164  if (s == nullptr)
165  return false;
166 
167  const QString &nameFilter = d->filterInfo.serverName;
168  if (!nameFilter.isEmpty())
169  {
170  if (!s->name().contains(nameFilter, Qt::CaseInsensitive))
171  return false;
172  }
173 
174  if (!d->filterInfo.bEnabled)
175  return true;
176 
177  if (!d->filterInfo.addresses.matches(s->address()))
178  return false;
179 
180  if (!s->isKnown())
181  {
182  if (d->filterInfo.bShowOnlyValid)
183  return false;
184  if ((s->lastResponse() == Server::RESPONSE_BAD
185  || s->lastResponse() == Server::RESPONSE_TIMEOUT)
186  && !d->filterInfo.bShowNotRespondingServers)
187  {
188  return false;
189  }
190  if (s->lastResponse() == Server::RESPONSE_WAIT
191  && !d->filterInfo.bShowTooSoonServers)
192  {
193  return false;
194  }
195  if (s->lastResponse() == Server::RESPONSE_BANNED
196  && !d->filterInfo.bShowBannedServers)
197  {
198  return false;
199  }
200  }
201  else
202  {
203  // To make sure we perform an 'AND' query here all operations
204  // must be negative. This means that if their test succeeds,
205  // false must be returned.
206  //
207  // The ServerListFilterInfo copy constructor and operator= make
208  // sure that all strings are trimmed.
209  if (!d->filterInfo.bShowEmpty && s->isEmpty())
210  return false;
211 
212  if (!d->filterInfo.bShowFull && s->isFull())
213  return false;
214 
215  if (d->filterInfo.maxPing > 0 && d->filterInfo.maxPing < s->ping())
216  return false;
217 
218  if (d->filterInfo.lockedServers == Doomseeker::ShowOnly && !s->isLockedAnywhere())
219  return false;
220  else if (d->filterInfo.lockedServers == Doomseeker::ShowNone && s->isLockedAnywhere())
221  return false;
222 
223  if (d->filterInfo.testingServers == Doomseeker::ShowOnly && !s->isTestingServer())
224  return false;
225  else if (d->filterInfo.testingServers == Doomseeker::ShowNone && s->isTestingServer())
226  return false;
227 
228  if (!d->filterInfo.gameModes.isEmpty())
229  {
230  if (!d->filterInfo.gameModes.contains(s->gameMode().name(), Qt::CaseInsensitive))
231  return false;
232  }
233 
234  if (d->filterInfo.gameModesExcluded.contains(s->gameMode().name(), Qt::CaseInsensitive))
235  return false;
236 
237  if (!d->filterInfo.wads.isEmpty())
238  {
239  bool bWadFound = false;
240 
241  // TODO
242  // This may cause performance drops. Testing is required
243  for (const QString &filteredWad : d->filterInfo.wads)
244  {
245  if (s->anyWadnameContains(filteredWad))
246  {
247  bWadFound = true;
248  break;
249  }
250  }
251 
252  if (!bWadFound)
253  return false;
254  }
255 
256  if (!d->filterInfo.wadsExcluded.isEmpty())
257  {
258  for (const QString &filteredWad : d->filterInfo.wadsExcluded)
259  {
260  if (s->anyWadnameContains(filteredWad))
261  return false;
262  }
263  }
264  }
265 
266  return true;
267 }
268 
269 const ServerListFilterInfo &ServerListProxyModel::filterInfo() const
270 {
271  return d->filterInfo;
272 }
273 
274 bool ServerListProxyModel::isAnyColumnSortedAdditionally() const
275 {
276  return !d->additionalSortColumns.isEmpty();
277 }
278 
279 bool ServerListProxyModel::isSortingAdditionallyByColumn(int column) const
280 {
281  return d->additionalSortForColumn(column).isValid();
282 }
283 
284 bool ServerListProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
285 {
286  if (!d->parentHandler->getMainWindow()->isEffectivelyActiveWindow())
287  return false;
288 
289  auto model = static_cast<ServerListModel *>(sourceModel());
290 
291  ServerPtr s1 = serverFromList(left);
292  ServerPtr s2 = serverFromList(right);
293 
294  if (s1 != nullptr && s2 != nullptr)
295  {
296  if (s1->isCustom() && !s2->isCustom())
297  return d->sortOrder == Qt::AscendingOrder;
298  else if (!s1->isCustom() && s2->isCustom())
299  return d->sortOrder == Qt::DescendingOrder;
300 
301  if (s1->isLan() && !s2->isLan())
302  return d->sortOrder == Qt::AscendingOrder;
303  else if (!s1->isLan() && s2->isLan())
304  return d->sortOrder == Qt::DescendingOrder;
305  }
306 
307  ServerListModel::ServerGroup sg1 = model->serverGroup(left.row());
308  ServerListModel::ServerGroup sg2 = model->serverGroup(right.row());
309 
310  if (sg1 != sg2 && sg1 != 0 && sg2 != 0)
311  {
312  if (sg1 > sg2)
313  return d->sortOrder == Qt::AscendingOrder;
314  else
315  return d->sortOrder == Qt::DescendingOrder;
316  }
317 
318  if (d->filterInfo.bPopulatedServersOnTop)
319  {
320  // Using data stored in column will honor user settings declaring
321  // whether bots should be treated as players or not.
322  int numPlayers1 = sourceModel()->data(left.sibling(left.row(),
323  ServerListColumnId::IDPlayers), sortRole()).toInt();
324  int numPlayers2 = sourceModel()->data(right.sibling(right.row(),
325  ServerListColumnId::IDPlayers), sortRole()).toInt();
326  if (numPlayers1 > 0 && numPlayers2 == 0)
327  return d->sortOrder == Qt::AscendingOrder;
328  else if (numPlayers1 == 0 && numPlayers2 > 0)
329  return d->sortOrder == Qt::DescendingOrder;
330  }
331 
332  QVariant leftVar = sourceModel()->data(left, sortRole());
333  QVariant rightVar = sourceModel()->data(right, sortRole());
334  int comparison = compareColumnSortData(leftVar, rightVar, left.column());
335  if (comparison == 0)
336  {
337  for (const ColumnSort &additionalSort : d->additionalSortColumns)
338  {
339  QModelIndex additionalLeft = left.sibling(left.row(), additionalSort.columnId());
340  QModelIndex additionalRight = right.sibling(right.row(), additionalSort.columnId());
341  leftVar = sourceModel()->data(additionalLeft, sortRole());
342  rightVar = sourceModel()->data(additionalRight, sortRole());
343  comparison = compareColumnSortData(leftVar, rightVar, additionalSort.columnId());
344  if (comparison != 0)
345  {
346  if (additionalSort.order() == Qt::DescendingOrder)
347  comparison *= -1;
348  break;
349  }
350  }
351  }
352  return comparison < 0;
353 }
354 
355 void ServerListProxyModel::removeAdditionalColumnSorting(int column)
356 {
357  bool anythingRemoved = d->removeAdditionalColumnSorting(column);
358  if (d->mainSortColumn > 0)
359  sort(d->mainSortColumn, d->sortOrder);
360  if (anythingRemoved)
361  emit additionalSortColumnsChanged();
362 }
363 
365 {
366  d->filterInfo = filterInfo;
367  invalidate();
368 }
369 
370 ServerPtr ServerListProxyModel::serverFromList(const QModelIndex &index) const
371 {
372  return serverFromList(index.row());
373 }
374 
375 ServerPtr ServerListProxyModel::serverFromList(int row) const
376 {
377  auto model = static_cast<ServerListModel *>(sourceModel());
378  return model->serverFromList(row);
379 }
380 
381 void ServerListProxyModel::setAdditionalSortColumns(const QList<ColumnSort> &columns)
382 {
383  d->additionalSortColumns = columns;
384  emit additionalSortColumnsChanged();
385 }
386 
387 void ServerListProxyModel::sortServers(int column, Qt::SortOrder order)
388 {
389  d->mainSortColumn = column;
390  d->sortOrder = order;
391  if (d->removeAdditionalColumnSorting(column))
392  emit additionalSortColumnsChanged();
393  sort(column, order);
394 }
396 ColumnSort::ColumnSort()
397 {
398  columnId_ = -1;
399  order_ = Qt::AscendingOrder;
400 }
401 
402 ColumnSort::ColumnSort(int columnId, Qt::SortOrder order)
403 {
404  columnId_ = columnId;
405  order_ = order;
406 }
407 
408 int ColumnSort::columnId() const
409 {
410  return columnId_;
411 }
412 
413 ColumnSort ColumnSort::deserializeQVariant(const QVariant &v)
414 {
415  QVariantMap map = v.toMap();
416  return ColumnSort(map["columnId"].toInt(),
417  static_cast<Qt::SortOrder>(map["order"].toInt())
418  );
419 }
420 
421 bool ColumnSort::isValid() const
422 {
423  return columnId() >= 0;
424 }
425 
426 Qt::SortOrder ColumnSort::order() const
427 {
428  return order_;
429 }
430 
431 bool ColumnSort::operator==(const ColumnSort &other) const
432 {
433  return order() == other.order() && columnId() == other.columnId();
434 }
435 
436 QVariant ColumnSort::serializeQVariant() const
437 {
438  QVariantMap map;
439  map["columnId"] = columnId();
440  map["order"] = order();
441  return map;
442 }