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 (!s->isKnown())
178  {
179  if (d->filterInfo.bShowOnlyValid)
180  return false;
181  if ((s->lastResponse() == Server::RESPONSE_BAD
182  || s->lastResponse() == Server::RESPONSE_TIMEOUT)
183  && !d->filterInfo.bShowNotRespondingServers)
184  {
185  return false;
186  }
187  if (s->lastResponse() == Server::RESPONSE_WAIT
188  && !d->filterInfo.bShowTooSoonServers)
189  {
190  return false;
191  }
192  if (s->lastResponse() == Server::RESPONSE_BANNED
193  && !d->filterInfo.bShowBannedServers)
194  {
195  return false;
196  }
197  }
198  else
199  {
200  // To make sure we perform an 'AND' query here all operations
201  // must be negative. This means that if their test succeeds,
202  // false must be returned.
203  //
204  // The ServerListFilterInfo copy constructor and operator= make
205  // sure that all strings are trimmed.
206  if (!d->filterInfo.bShowEmpty && s->isEmpty())
207  return false;
208 
209  if (!d->filterInfo.bShowFull && s->isFull())
210  return false;
211 
212  if (d->filterInfo.maxPing > 0 && d->filterInfo.maxPing < s->ping())
213  return false;
214 
215  if (d->filterInfo.lockedServers == Doomseeker::ShowOnly && !s->isLockedAnywhere())
216  return false;
217  else if (d->filterInfo.lockedServers == Doomseeker::ShowNone && s->isLockedAnywhere())
218  return false;
219 
220  if (d->filterInfo.testingServers == Doomseeker::ShowOnly && !s->isTestingServer())
221  return false;
222  else if (d->filterInfo.testingServers == Doomseeker::ShowNone && s->isTestingServer())
223  return false;
224 
225  if (!d->filterInfo.gameModes.isEmpty())
226  {
227  if (!d->filterInfo.gameModes.contains(s->gameMode().name(), Qt::CaseInsensitive))
228  return false;
229  }
230 
231  if (d->filterInfo.gameModesExcluded.contains(s->gameMode().name(), Qt::CaseInsensitive))
232  return false;
233 
234  if (!d->filterInfo.wads.isEmpty())
235  {
236  bool bWadFound = false;
237 
238  // TODO
239  // This may cause performance drops. Testing is required
240  for (const QString &filteredWad : d->filterInfo.wads)
241  {
242  if (s->anyWadnameContains(filteredWad))
243  {
244  bWadFound = true;
245  break;
246  }
247  }
248 
249  if (!bWadFound)
250  return false;
251  }
252 
253  if (!d->filterInfo.wadsExcluded.isEmpty())
254  {
255  for (const QString &filteredWad : d->filterInfo.wadsExcluded)
256  {
257  if (s->anyWadnameContains(filteredWad))
258  return false;
259  }
260  }
261  }
262 
263  return true;
264 }
265 
266 const ServerListFilterInfo &ServerListProxyModel::filterInfo() const
267 {
268  return d->filterInfo;
269 }
270 
271 bool ServerListProxyModel::isAnyColumnSortedAdditionally() const
272 {
273  return !d->additionalSortColumns.isEmpty();
274 }
275 
276 bool ServerListProxyModel::isSortingAdditionallyByColumn(int column) const
277 {
278  return d->additionalSortForColumn(column).isValid();
279 }
280 
281 bool ServerListProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
282 {
283  if (!d->parentHandler->getMainWindow()->isEffectivelyActiveWindow())
284  return false;
285 
286  auto model = static_cast<ServerListModel *>(sourceModel());
287 
288  ServerPtr s1 = serverFromList(left);
289  ServerPtr s2 = serverFromList(right);
290 
291  if (s1 != nullptr && s2 != nullptr)
292  {
293  if (s1->isCustom() && !s2->isCustom())
294  return d->sortOrder == Qt::AscendingOrder;
295  else if (!s1->isCustom() && s2->isCustom())
296  return d->sortOrder == Qt::DescendingOrder;
297 
298  if (s1->isLan() && !s2->isLan())
299  return d->sortOrder == Qt::AscendingOrder;
300  else if (!s1->isLan() && s2->isLan())
301  return d->sortOrder == Qt::DescendingOrder;
302  }
303 
304  ServerListModel::ServerGroup sg1 = model->serverGroup(left.row());
305  ServerListModel::ServerGroup sg2 = model->serverGroup(right.row());
306 
307  if (sg1 != sg2 && sg1 != 0 && sg2 != 0)
308  {
309  if (sg1 > sg2)
310  return d->sortOrder == Qt::AscendingOrder;
311  else
312  return d->sortOrder == Qt::DescendingOrder;
313  }
314 
315  if (d->filterInfo.bPopulatedServersOnTop)
316  {
317  // Using data stored in column will honor user settings declaring
318  // whether bots should be treated as players or not.
319  int numPlayers1 = sourceModel()->data(left.sibling(left.row(),
320  ServerListColumnId::IDPlayers), sortRole()).toInt();
321  int numPlayers2 = sourceModel()->data(right.sibling(right.row(),
322  ServerListColumnId::IDPlayers), sortRole()).toInt();
323  if (numPlayers1 > 0 && numPlayers2 == 0)
324  return d->sortOrder == Qt::AscendingOrder;
325  else if (numPlayers1 == 0 && numPlayers2 > 0)
326  return d->sortOrder == Qt::DescendingOrder;
327  }
328 
329  QVariant leftVar = sourceModel()->data(left, sortRole());
330  QVariant rightVar = sourceModel()->data(right, sortRole());
331  int comparison = compareColumnSortData(leftVar, rightVar, left.column());
332  if (comparison == 0)
333  {
334  for (const ColumnSort &additionalSort : d->additionalSortColumns)
335  {
336  QModelIndex additionalLeft = left.sibling(left.row(), additionalSort.columnId());
337  QModelIndex additionalRight = right.sibling(right.row(), additionalSort.columnId());
338  leftVar = sourceModel()->data(additionalLeft, sortRole());
339  rightVar = sourceModel()->data(additionalRight, sortRole());
340  comparison = compareColumnSortData(leftVar, rightVar, additionalSort.columnId());
341  if (comparison != 0)
342  {
343  if (additionalSort.order() == Qt::DescendingOrder)
344  comparison *= -1;
345  break;
346  }
347  }
348  }
349  return comparison < 0;
350 }
351 
352 void ServerListProxyModel::removeAdditionalColumnSorting(int column)
353 {
354  bool anythingRemoved = d->removeAdditionalColumnSorting(column);
355  if (d->mainSortColumn > 0)
356  sort(d->mainSortColumn, d->sortOrder);
357  if (anythingRemoved)
358  emit additionalSortColumnsChanged();
359 }
360 
362 {
363  d->filterInfo = filterInfo;
364  invalidate();
365 }
366 
367 ServerPtr ServerListProxyModel::serverFromList(const QModelIndex &index) const
368 {
369  return serverFromList(index.row());
370 }
371 
372 ServerPtr ServerListProxyModel::serverFromList(int row) const
373 {
374  auto model = static_cast<ServerListModel *>(sourceModel());
375  return model->serverFromList(row);
376 }
377 
378 void ServerListProxyModel::setAdditionalSortColumns(const QList<ColumnSort> &columns)
379 {
380  d->additionalSortColumns = columns;
381  emit additionalSortColumnsChanged();
382 }
383 
384 void ServerListProxyModel::sortServers(int column, Qt::SortOrder order)
385 {
386  d->mainSortColumn = column;
387  d->sortOrder = order;
388  if (d->removeAdditionalColumnSorting(column))
389  emit additionalSortColumnsChanged();
390  sort(column, order);
391 }
393 ColumnSort::ColumnSort()
394 {
395  columnId_ = -1;
396  order_ = Qt::AscendingOrder;
397 }
398 
399 ColumnSort::ColumnSort(int columnId, Qt::SortOrder order)
400 {
401  columnId_ = columnId;
402  order_ = order;
403 }
404 
405 int ColumnSort::columnId() const
406 {
407  return columnId_;
408 }
409 
410 ColumnSort ColumnSort::deserializeQVariant(const QVariant &v)
411 {
412  QVariantMap map = v.toMap();
413  return ColumnSort(map["columnId"].toInt(),
414  static_cast<Qt::SortOrder>(map["order"].toInt())
415  );
416 }
417 
418 bool ColumnSort::isValid() const
419 {
420  return columnId() >= 0;
421 }
422 
423 Qt::SortOrder ColumnSort::order() const
424 {
425  return order_;
426 }
427 
428 bool ColumnSort::operator==(const ColumnSort &other) const
429 {
430  return order() == other.order() && columnId() == other.columnId();
431 }
432 
433 QVariant ColumnSort::serializeQVariant() const
434 {
435  QVariantMap map;
436  map["columnId"] = columnId();
437  map["order"] = order();
438  return map;
439 }