dockBuddiesList.cpp
1 //------------------------------------------------------------------------------
2 // dockBuddiesList.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) 2009 Braden "Blzut3" Obrzut <admin@maniacsvault.net>
22 //------------------------------------------------------------------------------
23 #include "configuration/doomseekerconfig.h"
24 #include "dockBuddiesList.h"
25 #include "patternlist.h"
26 #include "serverapi/mastermanager.h"
27 #include "serverapi/playerslist.h"
28 #include "serverapi/server.h"
29 #include "strings.hpp"
30 #include "ui_addBuddyDlg.h"
31 #include "ui_dockBuddiesList.h"
32 #include <QMenu>
33 #include <QMessageBox>
34 #include <QRegularExpression>
35 #include <QStandardItemModel>
36 
37 class BuddyLocationInfo
38 {
39 public:
40  BuddyLocationInfo(const Player &buddy, ServerPtr location);
41  ~BuddyLocationInfo();
42 
43  const Player &buddy() const;
44  ServerPtr location() const;
45 
46  bool operator==(const BuddyLocationInfo &other) const;
47 
48 private:
50 };
51 
52 // [BL] This was moved earlier in the file in order to prevent double specialization
53 // with some compilers
54 DClass<BuddyLocationInfo>
55 {
56 public:
57  Player buddy;
58  ServerPtr server;
59 };
60 
61 DPointered(BuddyLocationInfo)
62 
63 DClass<DockBuddiesList> : public Ui::DockBuddiesList
64 {
65 public:
66  QList<BuddyLocationInfo> buddies;
67  QStandardItemModel *buddiesTableModel;
68  PatternList patterns;
69 };
70 
71 DPointered(DockBuddiesList)
72 
73 DockBuddiesList::DockBuddiesList(QWidget *parent)
74  : QDockWidget(parent), masterClient(nullptr), save(false)
75 {
76  d->setupUi(this);
77  this->toggleViewAction()->setIcon(QIcon(":/icons/buddies.png"));
78 
79  // Set up the model
80  d->buddiesTableModel = new QStandardItemModel();
81 
82  // Not working yet.
83  QStringList columns;
84  columns << DockBuddiesList::tr("ID");
85  columns << DockBuddiesList::tr("Buddy");
86  columns << DockBuddiesList::tr("Location");
87  d->buddiesTableModel->setHorizontalHeaderLabels(columns);
88 
89  d->buddiesTable->setModel(d->buddiesTableModel);
90  d->buddiesTable->setColumnHidden(0, true); // Hide the ID
91 
92  // Read config
93  d->patterns = gConfig.doomseeker.buddies;
94 
95  for (const auto &pattern : d->patterns)
96  {
97  d->patternsList->addItem(pattern.userPattern());
98  }
99 
100  connect(d->addButton, SIGNAL(clicked()), this, SLOT(addBuddy()));
101  connect(d->buddiesTable, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(followBuddy(const QModelIndex&)));
102  connect(d->deleteButton, SIGNAL(clicked()), this, SLOT(deleteBuddy()));
103  connect(d->patternsList, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(patternsListContextMenu(const QPoint&)));
104 }
105 
106 DockBuddiesList::~DockBuddiesList()
107 {
108  // See if we made any modification since modifying the setting will cause a
109  // write cycle.
110  if (save)
111  gConfig.doomseeker.buddies = d->patterns;
112 }
113 
114 void DockBuddiesList::addBuddy()
115 {
116  AddBuddyDlg dlg;
117  int result = dlg.exec();
118  if (result != QDialog::Accepted)
119  return;
120 
121  auto pattern = dlg.pattern();
122  d->patterns << pattern;
123  d->patternsList->addItem(pattern.userPattern());
124 
125  scan(masterClient);
126 
127  save = true;
128 }
129 
130 void DockBuddiesList::deleteBuddy()
131 {
132  // Do nothing if nothing is selected.
133  if (d->patternsList->selectionModel()->selectedIndexes().size() <= 0)
134  return;
135 
136  if (QMessageBox::warning(this, tr("Remove Buddy"), tr("Are you sure you want to remove this pattern?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)
137  != QMessageBox::Yes)
138  return;
139 
140  QModelIndexList selection = d->patternsList->selectionModel()->selectedIndexes();
141  // We have to remove the bottom most row until they are all gone.
142  while (selection.size() > 0)
143  {
144  int largestIndex = 0;
145  for (int i = 1; i < selection.size(); i++)
146  {
147  if (selection[i].row() > selection[largestIndex].row())
148  largestIndex = i;
149  }
150 
151  d->patterns.removeAt(selection[largestIndex].row());
152  delete d->patternsList->item(selection[largestIndex].row());
153  selection.removeAt(largestIndex); // remove index
154  }
155 
156  scan(masterClient);
157 
158  save = true;
159 }
160 
161 void DockBuddiesList::followBuddy(const QModelIndex &index)
162 {
163  // Folow the buddy into the server.
164  QString error;
165 
166  ServerPtr server = d->buddies[d->buddiesTableModel->item(index.row(), BLCID_ID)->data().toInt()].location();
167  emit joinServer(server);
168 }
169 
170 bool DockBuddiesList::hasBuddy(const ServerPtr &server) const
171 {
172  for (const BuddyLocationInfo &location : d->buddies)
173  {
174  if (location.location() == server)
175  return true;
176  }
177  return false;
178 }
179 
180 bool DockBuddiesList::isBuddy(const Player &player) const
181 {
182  for (const BuddyLocationInfo &location : d->buddies)
183  {
184  if (location.buddy() == player)
185  return true;
186  }
187  return false;
188 }
189 
190 void DockBuddiesList::patternsListContextMenu(const QPoint &pos) const
191 {
192  // First lets get the selection since that will determine the menu.
193  QModelIndexList selection = d->patternsList->selectionModel()->selectedIndexes();
194 
195  QMenu context;
196 
197  // Always have the add function.
198  QAction *addAction = context.addAction(tr("Add"));
199  connect(addAction, SIGNAL(triggered()), this, SLOT(addBuddy()));
200 
201  // if anything is selected allow the user to delete them.
202  if (selection.size() > 0)
203  {
204  QAction *deleteAction = context.addAction(tr("Remove"));
205  connect(deleteAction, SIGNAL(triggered()), this, SLOT(deleteBuddy()));
206  }
207 
208  // Finally show our menu.
209  context.exec(d->patternsList->mapToGlobal(pos));
210 }
211 
212 void DockBuddiesList::scan(const MasterManager *master)
213 {
214  if (master == nullptr && masterClient == nullptr)
215  return;
216  else if (master != masterClient && master != nullptr) // If the master is new
217  masterClient = master;
218 
219  d->buddies.clear(); //empty list
220  for (ServerPtr server : masterClient->allServers())
221  {
222  if (!server->isKnown())
223  continue;
224  for (int i = 0; i < server->players().numClients(); ++i)
225  {
226  const Player player = server->player(i);
227  if (d->patterns.isExactMatchAny(player.nameColorTagsStripped()))
228  {
229  BuddyLocationInfo candidate(player, server);
230  if (!d->buddies.contains(candidate))
231  d->buddies << BuddyLocationInfo(player, server);
232  }
233  }
234  }
235 
236  // Populate list
237  d->buddiesTableModel->removeRows(0, d->buddiesTableModel->rowCount());
238  for (int i = 0; i < d->buddies.size(); i++)
239  {
240  const BuddyLocationInfo &info = d->buddies[i];
241  QList<QStandardItem *> columns;
242 
243  for (int j = 0; j < HOW_MANY_BUDDIESLIST_COLUMNS; j++)
244  {
245  switch (j)
246  {
247  case BLCID_ID:
248  {
249  auto item = new QStandardItem();
250  item->setData(i);
251  columns << item;
252  break;
253  }
254  case BLCID_BUDDY:
255  columns << new QStandardItem(info.buddy().nameColorTagsStripped());
256  break;
257  case BLCID_LOCATION:
258  columns << new QStandardItem(info.location()->name());
259  break;
260  default:
261  columns << new QStandardItem();
262  break;
263  }
264  }
265 
266  d->buddiesTableModel->appendRow(columns);
267  }
268 
269  // Fits rows to contents
270  d->buddiesTable->resizeRowsToContents();
271  emit scanCompleted();
272 }
273 
275 
276 DClass<AddBuddyDlg> : public Ui::AddBuddyDlg
277 {
278 };
279 
280 DPointered(AddBuddyDlg)
281 
282 AddBuddyDlg::AddBuddyDlg(QWidget *parent) : QDialog(parent)
283 {
284  d->setupUi(this);
285 
286  connect(d->buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(buttonBoxClicked(QAbstractButton*)));
287  updateTip();
288 }
289 
290 AddBuddyDlg::~AddBuddyDlg()
291 {
292 }
293 
294 void AddBuddyDlg::buttonBoxClicked(QAbstractButton *button)
295 {
296  if (d->buttonBox->standardButton(button) == QDialogButtonBox::Ok)
297  {
298  if (!pattern().isValid())
299  {
300  QMessageBox::information(this, tr("Invalid Pattern"), tr("The pattern you have specified is invalid."));
301  return;
302  }
303  accept();
304  }
305  else
306  reject();
307 }
308 
309 Pattern AddBuddyDlg::pattern() const
310 {
311  auto syntax = d->basicPattern->isChecked() ? Pattern::Wildcard : Pattern::RegExp;
312  return Pattern(d->patternBox->text(), QRegularExpression::CaseInsensitiveOption, syntax);
313 }
314 
315 void AddBuddyDlg::updateTip()
316 {
317  if (d->basicPattern->isChecked())
318  {
319  d->lblTip->setText(tr("The asterisk (*) can be used as a wild card."));
320  }
321  else if (d->advancedPattern->isChecked())
322  {
323  d->lblTip->setText(tr("Use the Regular Expression format."));
324  }
325  else
326  {
327  d->lblTip->setText("");
328  }
329 }
330 
332 
333 BuddyLocationInfo::BuddyLocationInfo(const Player &buddy, ServerPtr location)
334 {
335  d->buddy = buddy;
336  d->server = location;
337 }
338 
339 BuddyLocationInfo::~BuddyLocationInfo()
340 {
341 }
342 
343 const Player &BuddyLocationInfo::buddy() const
344 {
345  return d->buddy;
346 }
347 
348 ServerPtr BuddyLocationInfo::location() const
349 {
350  return d->server;
351 }
352 
353 bool BuddyLocationInfo::operator==(const BuddyLocationInfo &other) const
354 {
355  return d->buddy == other.d->buddy
356  && d->server->address() == other.d->server->address()
357  && d->server->port() == other.d->server->port()
358  && d->server->plugin() == other.d->server->plugin();
359 }