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