serverfilterdock.cpp
1 //------------------------------------------------------------------------------
2 // serverfilterdock.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 "serverfilterdock.h"
24 #include "ui_serverfilterdock.h"
25 
26 #include "configuration/doomseekerconfig.h"
27 #include "gui/commongui.h"
28 #include "gui/entity/addressfilter.h"
29 #include "gui/entity/serverlistfilterinfo.h"
30 #include "gui/icons.h"
31 #include "strings.hpp"
32 
33 #include <QAction>
34 #include <QMessageBox>
35 #include <QTimer>
36 
37 DClass<ServerFilterDock> : public Ui::ServerFilterDock
38 {
39 public:
40  static const int CUSTOM_PRESET_IDX = 0;
41 
49  QLineEdit *leQuickSearch;
54  bool bDisableUpdate;
55 
56  void clearCustomPresets()
57  {
58  cboServerFilterPresets->blockSignals(true);
59  for (int i = cboServerFilterPresets->count() - 1;
60  i > CUSTOM_PRESET_IDX;
61  --i)
62  {
63  cboServerFilterPresets->removeItem(i);
64  }
65  cboServerFilterPresets->blockSignals(false);
66  }
67 
68  bool isCustomPreset(const ServerListFilterInfo &preset) const
69  {
70  return isCustomPresetName(preset.name);
71  }
72 
73  bool isCustomPresetName(const QString &name) const
74  {
75  return name.isEmpty();
76  }
77 
78  bool isCustomPresetIndex(int index) const
79  {
80  return index == CUSTOM_PRESET_IDX;
81  }
82 
83  bool isCustomPresetSelected() const
84  {
85  return isCustomPresetIndex(cboServerFilterPresets->currentIndex());
86  }
87 
88  ServerListFilterInfo presetAtIndex(int index) const
89  {
90  return ServerListFilterInfo::deserialize(cboServerFilterPresets->itemData(index));
91  }
92 
93  ServerListFilterInfo currentPreset() const
94  {
95  return presetAtIndex(cboServerFilterPresets->currentIndex());
96  }
97 
98  QString presetNameAtIndex(int index) const
99  {
100  return isCustomPresetIndex(index) ?
101  QString() : presetAtIndex(index).name;
102  }
103 
104  int presetIndex(const QString &name) const
105  {
106  if (isCustomPresetName(name))
107  return CUSTOM_PRESET_IDX;
108  for (int i = CUSTOM_PRESET_IDX + 1; i < cboServerFilterPresets->count(); ++i)
109  {
110  if (presetAtIndex(i).name.compare(name, Qt::CaseInsensitive) == 0)
111  {
112  return i;
113  }
114  }
115  return -1;
116  }
117 
118  QString currentPresetName() const
119  {
120  return presetNameAtIndex(cboServerFilterPresets->currentIndex());
121  }
122 
123  AddressFilter parseSubnets(const QString &subnets) const
124  {
125  QStringList tokens = subnets.split(",");
126  AddressFilter result;
127  for (const QString &token : tokens)
128  {
129  QPair<QHostAddress, int> subnet = QHostAddress::parseSubnet(token.trimmed());
130  if (!subnet.first.isNull())
131  {
132  result << subnet;
133  }
134  }
135  return result;
136  }
137 };
138 
139 DPointeredNoCopy(ServerFilterDock)
140 
141 ServerFilterDock::ServerFilterDock(QWidget *pParent)
142  : QDockWidget(pParent)
143 {
144  d->setupUi(this);
145  d->leQuickSearch = nullptr;
146  d->bDisableUpdate = false;
147 
148  d->btnClear->setIcon(Icons::clear());
149  toggleViewAction()->setIcon(QIcon(":/icons/filter.png"));
150 
151  toggleViewAction()->setText(ServerFilterDock::tr("Server &filter"));
152  toggleViewAction()->setShortcut(ServerFilterDock::tr("CTRL+F"));
153 
154  QTimer::singleShot(0, this, &ServerFilterDock::loadSettings);
155 }
156 
157 ServerFilterDock::~ServerFilterDock()
158 {
159 }
160 
161 void ServerFilterDock::addGameModeToComboBox(const QString &gameMode)
162 {
163  addSortedNonDuplicate(d->cboGameMode, gameMode.trimmed());
164  addSortedNonDuplicate(d->cboExcludeGameMode, gameMode.trimmed());
165 }
166 
167 void ServerFilterDock::addSortedNonDuplicate(QComboBox *comboBox, const QString &text)
168 {
169  if (comboBox->findText(text, Qt::MatchFixedString) < 0)
170  {
171  // Make sure combobox contents are sorted.
172  for (int i = 0; i < comboBox->count(); ++i)
173  {
174  if (text < comboBox->itemText(i))
175  {
176  comboBox->insertItem(i, text);
177  return;
178  }
179  }
180 
181  // The above routine didn't return.
182  // This item belongs to the end of the list.
183  comboBox->addItem(text);
184  }
185 }
186 
187 void ServerFilterDock::clear()
188 {
189  setCustomPreset(ServerListFilterInfo());
190  d->cboServerFilterPresets->setCurrentIndex(PrivData<ServerFilterDock>::CUSTOM_PRESET_IDX);
192 }
193 
195 {
196  if (d->leQuickSearch == nullptr)
197  {
198  auto qs = new QLineEdit();
199  qs->setText(d->leServerName->text());
200 
201  connect(d->leServerName, SIGNAL(textEdited(const QString&)), qs, SLOT(setText(const QString&)));
202  connect(qs, SIGNAL(textEdited(const QString&)), d->leServerName, SLOT(setText(const QString&)));
203 
204  d->leQuickSearch = qs;
205  }
206 
207  return d->leQuickSearch;
208 }
209 
210 void ServerFilterDock::emitUpdated()
211 {
212  if (d->bDisableUpdate)
213  return;
214 
215  ServerListFilterInfo filterInfo = this->filterInfo();
216  if (d->isCustomPresetSelected())
217  {
218  setCustomPreset(filterInfo);
219  }
220  else
221  {
222  const ServerListFilterInfo currentPreset = d->currentPreset();
223  d->cboServerFilterPresets->setItemText(d->cboServerFilterPresets->currentIndex(),
224  currentPreset.isFilteringEquivalent(filterInfo) ?
225  currentPreset.name :
226  QString("%1 *").arg(currentPreset.name));
227  }
228 
229  emit filterUpdated(filterInfo);
230 }
231 
232 void ServerFilterDock::enableFilter()
233 {
234  d->cbFilteringEnabled->setChecked(true);
235  emitUpdated();
236 }
237 
238 ServerListFilterInfo ServerFilterDock::filterInfo() const
239 {
240  ServerListFilterInfo filterInfo;
241 
242  filterInfo.bEnabled = d->cbFilteringEnabled->isChecked();
243  filterInfo.bPopulatedServersOnTop = d->cbGroupServersWithPlayersAtTop->isChecked();
244  filterInfo.bShowEmpty = d->cbShowEmpty->isChecked();
245  filterInfo.bShowFull = d->cbShowFull->isChecked();
246  filterInfo.bShowOnlyValid = d->cbShowOnlyValid->isChecked();
247  filterInfo.bShowBannedServers = d->cbShowBannedServers->isChecked();
248  filterInfo.bShowTooSoonServers = d->cbShowTooSoonServers->isChecked();
249  filterInfo.bShowNotRespondingServers = d->cbShowNotRespondingServers->isChecked();
250  filterInfo.addresses = d->parseSubnets(d->leAddresses->text());
251  filterInfo.gameModes = d->cboGameMode->selectedItemTexts();
252  filterInfo.gameModesExcluded = d->cboExcludeGameMode->selectedItemTexts();
253  filterInfo.lockedServers = Doomseeker::checkboxTristateToShowMode(d->cbShowLockedServers->checkState());
254  filterInfo.maxPing = d->spinMaxPing->value();
255  filterInfo.testingServers = Doomseeker::checkboxTristateToShowMode(d->cbShowTestingServers->checkState());
256  filterInfo.serverName = d->leServerName->text();
257  filterInfo.wads = d->leWads->text().trimmed().split(",", Qt::SkipEmptyParts);
258  filterInfo.wadsExcluded = d->leExcludeWads->text().trimmed().split(",", Qt::SkipEmptyParts);
259 
260  return filterInfo;
261 }
262 
263 int ServerFilterDock::addPreset(const ServerListFilterInfo &preset)
264 {
265  if (d->isCustomPreset(preset))
266  {
267  setCustomPreset(preset);
269  }
270 
271  int index = d->presetIndex(preset.name);
273  {
274  d->cboServerFilterPresets->setItemData(index, preset.serialize());
275  }
276  else
277  {
279  for (; index < d->cboServerFilterPresets->count(); ++index)
280  {
281  if (d->presetAtIndex(index).name.compare(
282  preset.name, Qt::CaseInsensitive) > 0)
283  {
284  break;
285  }
286  }
287  d->cboServerFilterPresets->insertItem(index, preset.name, preset.serialize());
288  }
289  return index;
290 }
291 
292 void ServerFilterDock::setCustomPreset(const ServerListFilterInfo &preset)
293 {
294  static const int idxCustom = PrivData<ServerFilterDock>::CUSTOM_PRESET_IDX;
295  d->cboServerFilterPresets->setItemText(idxCustom,
296  preset.isFilteringAnything() ? tr("[custom]") : tr("[no filter]"));
297  d->cboServerFilterPresets->setItemData(idxCustom, preset.serialize());
298 }
299 
301 {
302  d->bDisableUpdate = true;
303 
304  d->cbFilteringEnabled->setChecked(filterInfo.bEnabled);
305  d->cbGroupServersWithPlayersAtTop->setChecked(filterInfo.bPopulatedServersOnTop);
306  d->cbShowEmpty->setChecked(filterInfo.bShowEmpty);
307  d->cbShowFull->setChecked(filterInfo.bShowFull);
308  d->cbShowOnlyValid->setChecked(filterInfo.bShowOnlyValid);
309  d->cbShowBannedServers->setChecked(filterInfo.bShowBannedServers);
310  d->cbShowTooSoonServers->setChecked(filterInfo.bShowTooSoonServers);
311  d->cbShowNotRespondingServers->setChecked(filterInfo.bShowNotRespondingServers);
312 
313  d->leAddresses->setText(filterInfo.addresses.toString());
314 
315  for (const QString &gameMode : filterInfo.gameModes)
316  {
317  addGameModeToComboBox(gameMode);
318  }
319  d->cboGameMode->setSelectedTexts(filterInfo.gameModes);
320 
321  for (const QString &gameMode : filterInfo.gameModesExcluded)
322  {
323  addGameModeToComboBox(gameMode);
324  }
325  d->cboExcludeGameMode->setSelectedTexts(filterInfo.gameModesExcluded);
326 
327  d->spinMaxPing->setValue(filterInfo.maxPing);
328  if (d->leQuickSearch != nullptr)
329  d->leQuickSearch->setText(filterInfo.serverName.trimmed());
330 
331  d->leServerName->setText(filterInfo.serverName.trimmed());
332  d->leWads->setText(filterInfo.wads.join(",").trimmed());
333  d->leExcludeWads->setText(filterInfo.wadsExcluded.join(",").trimmed());
334 
335  d->cbShowLockedServers->setCheckState(Doomseeker::showModeToCheckboxState(
336  filterInfo.lockedServers));
337  d->cbShowTestingServers->setCheckState(Doomseeker::showModeToCheckboxState(
338  filterInfo.testingServers));
339 
340  d->bDisableUpdate = false;
341  emitUpdated();
342 }
343 
344 void ServerFilterDock::removeFilterPreset()
345 {
346  const QString name = d->currentPresetName();
347  if (QMessageBox::Yes == QMessageBox::question(this,
348  tr("Doomseeker - Remove filter preset"),
349  tr("Are you sure you wish to remove the filter preset \"%1\"?").arg(name)))
350  {
351  if (!name.isEmpty() && !d->isCustomPresetName(name))
352  {
353  gConfig.serverFilter.presets.remove(name);
354  d->cboServerFilterPresets->removeItem(d->presetIndex(name));
355  }
356  }
357 }
358 
359 void ServerFilterDock::saveFilterAsPreset()
360 {
361  bool ok = false;
362  const QString name = CommonGUI::getText(this, tr("Doomseeker - Save filter preset"),
363  tr("Enter name for the filter preset (will overwrite if exists):"),
364  QLineEdit::Normal, d->currentPresetName(), &ok).trimmed();
365 
366  if (!ok || name.isEmpty())
367  return;
368 
369  ServerListFilterInfo filter = this->filterInfo();
370  filter.name = name;
371  gConfig.serverFilter.presets[filter.name] = filter.serialize();
372  gConfig.saveToFile();
373  const int index = addPreset(filter);
374  d->cboServerFilterPresets->setCurrentIndex(index);
375  d->cboServerFilterPresets->setItemText(index, name); // reset the * if any
376 }
377 
378 void ServerFilterDock::loadSettings()
379 {
380  // Presets.
381  d->clearCustomPresets();
382  for (const QVariant &serialized : gConfig.serverFilter.presets)
383  {
384  ServerListFilterInfo preset = ServerListFilterInfo::deserialize(serialized);
385  if (!preset.name.isEmpty())
386  addPreset(preset);
387  }
388 
389  // The custom filter.
390  setCustomPreset(gConfig.serverFilter.info);
391 
392  // Now select the current filter.
393  int currentPresetIdx = d->presetIndex(gConfig.serverFilter.currentPreset);
394  if (currentPresetIdx < 0 || currentPresetIdx >= d->cboServerFilterPresets->count())
395  {
397  }
398  selectFilterPreset(currentPresetIdx);
399 }
400 
401 void ServerFilterDock::selectFilterPreset(int index)
402 {
403  d->cboServerFilterPresets->blockSignals(true);
404  d->cboServerFilterPresets->setCurrentIndex(index);
405  d->cboServerFilterPresets->blockSignals(false);
406  d->btnRemovePreset->setEnabled(!d->isCustomPresetIndex(index) && index > 0);
407  gConfig.serverFilter.currentPreset = d->currentPresetName();
409  i < d->cboServerFilterPresets->count();
410  ++i)
411  {
412  // Reset any '*'
413  d->cboServerFilterPresets->setItemText(i, d->presetNameAtIndex(i));
414  }
415  setFilterInfo(ServerListFilterInfo::deserialize(
416  d->cboServerFilterPresets->itemData(index)));
417 }