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