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