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