qBittorrent
peerlistwidget.cpp
Go to the documentation of this file.
1 /*
2  * Bittorrent Client using Qt and libtorrent.
3  * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
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 02110-1301, USA.
18  *
19  * In addition, as a special exception, the copyright holders give permission to
20  * link this program with the OpenSSL project's "OpenSSL" library (or with
21  * modified versions of it that use the same license as the "OpenSSL" library),
22  * and distribute the linked executables. You must obey the GNU General Public
23  * License in all respects for all of the code used other than "OpenSSL". If you
24  * modify file(s), you may extend this exception to your version of the file(s),
25  * but you are not obligated to do so. If you do not wish to do so, delete this
26  * exception statement from your version.
27  */
28 
29 #include "peerlistwidget.h"
30 
31 #include <algorithm>
32 
33 #include <QApplication>
34 #include <QClipboard>
35 #include <QHeaderView>
36 #include <QHostAddress>
37 #include <QMenu>
38 #include <QMessageBox>
39 #include <QSet>
40 #include <QShortcut>
41 #include <QSortFilterProxyModel>
42 #include <QStandardItemModel>
43 #include <QTableView>
44 #include <QVector>
45 #include <QWheelEvent>
46 
51 #include "base/global.h"
52 #include "base/logger.h"
53 #include "base/net/geoipmanager.h"
55 #include "base/preferences.h"
56 #include "base/utils/misc.h"
57 #include "base/utils/string.h"
58 #include "gui/uithememanager.h"
59 #include "peerlistsortmodel.h"
60 #include "peersadditiondialog.h"
61 #include "propertieswidget.h"
62 
64 {
66  QString connectionType; // matches return type of `PeerInfo::connectionType()`
67 };
68 
69 bool operator==(const PeerEndpoint &left, const PeerEndpoint &right)
70 {
71  return (left.address == right.address) && (left.connectionType == right.connectionType);
72 }
73 
74 uint qHash(const PeerEndpoint &peerEndpoint, const uint seed)
75 {
76  return (qHash(peerEndpoint.address, seed) ^ ::qHash(peerEndpoint.connectionType));
77 }
78 
80  : QTreeView(parent)
81  , m_properties(parent)
82 {
83  // Load settings
84  loadSettings();
85  // Visual settings
86  setUniformRowHeights(true);
87  setRootIsDecorated(false);
88  setItemsExpandable(false);
89  setAllColumnsShowFocus(true);
90  setEditTriggers(QAbstractItemView::NoEditTriggers);
91  setSelectionMode(QAbstractItemView::ExtendedSelection);
92  header()->setStretchLastSection(false);
93  header()->setTextElideMode(Qt::ElideRight);
94 
95  // List Model
96  m_listModel = new QStandardItemModel(0, PeerListColumns::COL_COUNT, this);
97  m_listModel->setHeaderData(PeerListColumns::COUNTRY, Qt::Horizontal, tr("Country/Region")); // Country flag column
98  m_listModel->setHeaderData(PeerListColumns::IP, Qt::Horizontal, tr("IP"));
99  m_listModel->setHeaderData(PeerListColumns::PORT, Qt::Horizontal, tr("Port"));
100  m_listModel->setHeaderData(PeerListColumns::FLAGS, Qt::Horizontal, tr("Flags"));
101  m_listModel->setHeaderData(PeerListColumns::CONNECTION, Qt::Horizontal, tr("Connection"));
102  m_listModel->setHeaderData(PeerListColumns::CLIENT, Qt::Horizontal, tr("Client", "i.e.: Client application"));
103  m_listModel->setHeaderData(PeerListColumns::PROGRESS, Qt::Horizontal, tr("Progress", "i.e: % downloaded"));
104  m_listModel->setHeaderData(PeerListColumns::DOWN_SPEED, Qt::Horizontal, tr("Down Speed", "i.e: Download speed"));
105  m_listModel->setHeaderData(PeerListColumns::UP_SPEED, Qt::Horizontal, tr("Up Speed", "i.e: Upload speed"));
106  m_listModel->setHeaderData(PeerListColumns::TOT_DOWN, Qt::Horizontal, tr("Downloaded", "i.e: total data downloaded"));
107  m_listModel->setHeaderData(PeerListColumns::TOT_UP, Qt::Horizontal, tr("Uploaded", "i.e: total data uploaded"));
108  m_listModel->setHeaderData(PeerListColumns::RELEVANCE, Qt::Horizontal, tr("Relevance", "i.e: How relevant this peer is to us. How many pieces it has that we don't."));
109  m_listModel->setHeaderData(PeerListColumns::DOWNLOADING_PIECE, Qt::Horizontal, tr("Files", "i.e. files that are being downloaded right now"));
110  // Set header text alignment
111  m_listModel->setHeaderData(PeerListColumns::PORT, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
112  m_listModel->setHeaderData(PeerListColumns::PROGRESS, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
113  m_listModel->setHeaderData(PeerListColumns::DOWN_SPEED, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
114  m_listModel->setHeaderData(PeerListColumns::UP_SPEED, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
115  m_listModel->setHeaderData(PeerListColumns::TOT_DOWN, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
116  m_listModel->setHeaderData(PeerListColumns::TOT_UP, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
117  m_listModel->setHeaderData(PeerListColumns::RELEVANCE, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
118  // Proxy model to support sorting without actually altering the underlying model
119  m_proxyModel = new PeerListSortModel(this);
120  m_proxyModel->setDynamicSortFilter(true);
121  m_proxyModel->setSourceModel(m_listModel);
122  m_proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
123  setModel(m_proxyModel);
124  hideColumn(PeerListColumns::IP_HIDDEN);
125  hideColumn(PeerListColumns::COL_COUNT);
127  if (!m_resolveCountries)
128  hideColumn(PeerListColumns::COUNTRY);
129  // Ensure that at least one column is visible at all times
130  bool atLeastOne = false;
131  for (int i = 0; i < PeerListColumns::IP_HIDDEN; ++i)
132  {
133  if (!isColumnHidden(i))
134  {
135  atLeastOne = true;
136  break;
137  }
138  }
139  if (!atLeastOne)
140  setColumnHidden(PeerListColumns::IP, false);
141  // To also mitigate the above issue, we have to resize each column when
142  // its size is 0, because explicitly 'showing' the column isn't enough
143  // in the above scenario.
144  for (int i = 0; i < PeerListColumns::IP_HIDDEN; ++i)
145  {
146  if ((columnWidth(i) <= 0) && !isColumnHidden(i))
147  resizeColumnToContents(i);
148  }
149  // Context menu
150  setContextMenuPolicy(Qt::CustomContextMenu);
151  connect(this, &QWidget::customContextMenuRequested, this, &PeerListWidget::showPeerListMenu);
152  // Enable sorting
153  setSortingEnabled(true);
154  // IP to Hostname resolver
156  // SIGNAL/SLOT
157  header()->setContextMenuPolicy(Qt::CustomContextMenu);
158  connect(header(), &QWidget::customContextMenuRequested, this, &PeerListWidget::displayToggleColumnsMenu);
159  connect(header(), &QHeaderView::sectionClicked, this, &PeerListWidget::handleSortColumnChanged);
160  connect(header(), &QHeaderView::sectionMoved, this, &PeerListWidget::saveSettings);
161  connect(header(), &QHeaderView::sectionResized, this, &PeerListWidget::saveSettings);
162  connect(header(), &QHeaderView::sortIndicatorChanged, this, &PeerListWidget::saveSettings);
163  handleSortColumnChanged(header()->sortIndicatorSection());
164  const auto *copyHotkey = new QShortcut(QKeySequence::Copy, this, nullptr, nullptr, Qt::WidgetShortcut);
165  connect(copyHotkey, &QShortcut::activated, this, &PeerListWidget::copySelectedPeers);
166 
167  // This hack fixes reordering of first column with Qt5.
168  // https://github.com/qtproject/qtbase/commit/e0fc088c0c8bc61dbcaf5928b24986cd61a22777
169  QTableView unused;
170  unused.setVerticalHeader(this->header());
171  this->header()->setParent(this);
172  unused.setVerticalHeader(new QHeaderView(Qt::Horizontal));
173 }
174 
176 {
177  saveSettings();
178 }
179 
181 {
182  QMenu *menu = new QMenu(this);
183  menu->setAttribute(Qt::WA_DeleteOnClose);
184  menu->setTitle(tr("Column visibility"));
185 
186  for (int i = 0; i < PeerListColumns::IP_HIDDEN; ++i)
187  {
188  if ((i == PeerListColumns::COUNTRY) && !Preferences::instance()->resolvePeerCountries())
189  continue;
190 
191  QAction *myAct = menu->addAction(m_listModel->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString());
192  myAct->setCheckable(true);
193  myAct->setChecked(!isColumnHidden(i));
194  myAct->setData(i);
195  }
196 
197  connect(menu, &QMenu::triggered, this, [this](const QAction *action)
198  {
199  int visibleCols = 0;
200  for (int i = 0; i < PeerListColumns::IP_HIDDEN; ++i)
201  {
202  if (!isColumnHidden(i))
203  ++visibleCols;
204 
205  if (visibleCols > 1)
206  break;
207  }
208 
209  const int col = action->data().toInt();
210 
211  if (!isColumnHidden(col) && (visibleCols == 1))
212  return;
213 
214  setColumnHidden(col, !isColumnHidden(col));
215 
216  if (!isColumnHidden(col) && (columnWidth(col) <= 5))
217  resizeColumnToContents(col);
218 
219  saveSettings();
220  });
221 
222  menu->popup(QCursor::pos());
223 }
224 
226 {
227  if (Preferences::instance()->resolvePeerHostNames())
228  {
229  if (!m_resolver)
230  {
234  }
235  }
236  else
237  {
238  delete m_resolver;
239  m_resolver = nullptr;
240  }
241 }
242 
244 {
245  const bool resolveCountries = Preferences::instance()->resolvePeerCountries();
246  if (resolveCountries == m_resolveCountries)
247  return;
248 
249  m_resolveCountries = resolveCountries;
250  if (m_resolveCountries)
251  {
253  showColumn(PeerListColumns::COUNTRY);
254  if (columnWidth(PeerListColumns::COUNTRY) <= 0)
255  resizeColumnToContents(PeerListColumns::COUNTRY);
256  }
257  else
258  {
259  hideColumn(PeerListColumns::COUNTRY);
260  }
261 }
262 
264 {
266  if (!torrent) return;
267 
268  auto *menu = new QMenu(this);
269  menu->setAttribute(Qt::WA_DeleteOnClose);
270  menu->setToolTipsVisible(true);
271 
272  QAction *addNewPeer = menu->addAction(UIThemeManager::instance()->getIcon("user-group-new"), tr("Add peers...")
273  , this, [this, torrent]()
274  {
275  const QVector<BitTorrent::PeerAddress> peersList = PeersAdditionDialog::askForPeers(this);
276  const int peerCount = std::count_if(peersList.cbegin(), peersList.cend(), [torrent](const BitTorrent::PeerAddress &peer)
277  {
278  return torrent->connectPeer(peer);
279  });
280  if (peerCount < peersList.length())
281  QMessageBox::information(this, tr("Adding peers"), tr("Some peers cannot be added. Check the Log for details."));
282  else if (peerCount > 0)
283  QMessageBox::information(this, tr("Adding peers"), tr("Peers are added to this torrent."));
284  });
285  QAction *copyPeers = menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy IP:port")
287  menu->addSeparator();
288  QAction *banPeers = menu->addAction(UIThemeManager::instance()->getIcon("user-group-delete"), tr("Ban peer permanently")
290 
291  // disable actions
292  const auto disableAction = [](QAction *action, const QString &tooltip)
293  {
294  action->setEnabled(false);
295  action->setToolTip(tooltip);
296  };
297 
298  if (torrent->isPrivate())
299  disableAction(addNewPeer, tr("Cannot add peers to a private torrent"));
300  else if (torrent->isChecking())
301  disableAction(addNewPeer, tr("Cannot add peers when the torrent is checking"));
302  else if (torrent->isQueued())
303  disableAction(addNewPeer, tr("Cannot add peers when the torrent is queued"));
304 
305  if (selectionModel()->selectedRows().isEmpty())
306  {
307  const QString tooltip = tr("No peer was selected");
308  disableAction(copyPeers, tooltip);
309  disableAction(banPeers, tooltip);
310  }
311 
312  menu->popup(QCursor::pos());
313 }
314 
316 {
317  // Store selected rows first as selected peers may disconnect
318  const QModelIndexList selectedIndexes = selectionModel()->selectedRows();
319 
320  QVector<QString> selectedIPs;
321  selectedIPs.reserve(selectedIndexes.size());
322 
323  for (const QModelIndex &index : selectedIndexes)
324  {
325  const int row = m_proxyModel->mapToSource(index).row();
326  const QString ip = m_listModel->item(row, PeerListColumns::IP_HIDDEN)->text();
327  selectedIPs += ip;
328  }
329 
330  // Confirm before banning peer
331  const QMessageBox::StandardButton btn = QMessageBox::question(this, tr("Ban peer permanently")
332  , tr("Are you sure you want to permanently ban the selected peers?"));
333  if (btn != QMessageBox::Yes) return;
334 
335  for (const QString &ip : selectedIPs)
336  {
338  LogMsg(tr("Peer \"%1\" is manually banned").arg(ip));
339  }
340  // Refresh list
342 }
343 
345 {
346  const QModelIndexList selectedIndexes = selectionModel()->selectedRows();
347  QStringList selectedPeers;
348 
349  for (const QModelIndex &index : selectedIndexes)
350  {
351  const int row = m_proxyModel->mapToSource(index).row();
352  const QString ip = m_listModel->item(row, PeerListColumns::IP_HIDDEN)->text();
353  const QString port = m_listModel->item(row, PeerListColumns::PORT)->text();
354 
355  if (!ip.contains('.')) // IPv6
356  selectedPeers << ('[' + ip + "]:" + port);
357  else // IPv4
358  selectedPeers << (ip + ':' + port);
359  }
360 
361  QApplication::clipboard()->setText(selectedPeers.join('\n'));
362 }
363 
365 {
366  m_peerItems.clear();
367  m_itemsByIP.clear();
368  const int nbrows = m_listModel->rowCount();
369  if (nbrows > 0)
370  m_listModel->removeRows(0, nbrows);
371 }
372 
374 {
375  header()->restoreState(Preferences::instance()->getPeerListState());
376 }
377 
379 {
380  Preferences::instance()->setPeerListState(header()->saveState());
381 }
382 
384 {
385  if (!torrent) return;
386 
387  const QVector<BitTorrent::PeerInfo> peers = torrent->peers();
388  QSet<PeerEndpoint> existingPeers;
389  for (auto i = m_peerItems.cbegin(); i != m_peerItems.cend(); ++i)
390  existingPeers << i.key();
391 
392  for (const BitTorrent::PeerInfo &peer : peers)
393  {
394  if (peer.address().ip.isNull()) continue;
395 
396  bool isNewPeer = false;
397  updatePeer(torrent, peer, isNewPeer);
398  if (!isNewPeer)
399  {
400  const PeerEndpoint peerEndpoint {peer.address(), peer.connectionType()};
401  existingPeers.remove(peerEndpoint);
402  }
403  }
404 
405  // Remove peers that are gone
406  for (const PeerEndpoint &peerEndpoint : asConst(existingPeers))
407  {
408  QStandardItem *item = m_peerItems.take(peerEndpoint);
409 
410  QSet<QStandardItem *> &items = m_itemsByIP[peerEndpoint.address.ip];
411  items.remove(item);
412  if (items.isEmpty())
413  m_itemsByIP.remove(peerEndpoint.address.ip);
414 
415  m_listModel->removeRow(item->row());
416  }
417 }
418 
419 void PeerListWidget::updatePeer(const BitTorrent::Torrent *torrent, const BitTorrent::PeerInfo &peer, bool &isNewPeer)
420 {
421  const PeerEndpoint peerEndpoint {peer.address(), peer.connectionType()};
422  const QString peerIp = peerEndpoint.address.ip.toString();
423  const Qt::Alignment intDataTextAlignment = Qt::AlignRight | Qt::AlignVCenter;
424 
425  const auto setModelData =
426  [this] (const int row, const int column, const QString &displayData
427  , const QVariant &underlyingData, const Qt::Alignment textAlignmentData = {}
428  , const QString &toolTip = {})
429  {
430  const QMap<int, QVariant> data =
431  {
432  {Qt::DisplayRole, displayData},
433  {PeerListSortModel::UnderlyingDataRole, underlyingData},
434  {Qt::TextAlignmentRole, QVariant {textAlignmentData}},
435  {Qt::ToolTipRole, toolTip}
436  };
437  m_listModel->setItemData(m_listModel->index(row, column), data);
438  };
439 
440  auto itemIter = m_peerItems.find(peerEndpoint);
441  isNewPeer = (itemIter == m_peerItems.end());
442  if (isNewPeer)
443  {
444  // new item
445  const int row = m_listModel->rowCount();
446  m_listModel->insertRow(row);
447 
448  setModelData(row, PeerListColumns::IP, peerIp, peerIp, {}, peerIp);
449  setModelData(row, PeerListColumns::PORT, QString::number(peer.address().port), peer.address().port, intDataTextAlignment);
450  setModelData(row, PeerListColumns::IP_HIDDEN, peerIp, peerIp);
451 
452  itemIter = m_peerItems.insert(peerEndpoint, m_listModel->item(row, PeerListColumns::IP));
453  m_itemsByIP[peerEndpoint.address.ip].insert(itemIter.value());
454  }
455 
456  const int row = (*itemIter)->row();
457  const bool hideValues = Preferences::instance()->getHideZeroValues();
458 
459  setModelData(row, PeerListColumns::CONNECTION, peer.connectionType(), peer.connectionType());
460  setModelData(row, PeerListColumns::FLAGS, peer.flags(), peer.flags(), {}, peer.flagsDescription());
461  const QString client = peer.client().toHtmlEscaped();
462  setModelData(row, PeerListColumns::CLIENT, client, client, {}, client);
463  setModelData(row, PeerListColumns::PROGRESS, (Utils::String::fromDouble(peer.progress() * 100, 1) + '%'), peer.progress(), intDataTextAlignment);
464  const QString downSpeed = (hideValues && (peer.payloadDownSpeed() <= 0)) ? QString {} : Utils::Misc::friendlyUnit(peer.payloadDownSpeed(), true);
465  setModelData(row, PeerListColumns::DOWN_SPEED, downSpeed, peer.payloadDownSpeed(), intDataTextAlignment);
466  const QString upSpeed = (hideValues && (peer.payloadUpSpeed() <= 0)) ? QString {} : Utils::Misc::friendlyUnit(peer.payloadUpSpeed(), true);
467  setModelData(row, PeerListColumns::UP_SPEED, upSpeed, peer.payloadUpSpeed(), intDataTextAlignment);
468  const QString totalDown = (hideValues && (peer.totalDownload() <= 0)) ? QString {} : Utils::Misc::friendlyUnit(peer.totalDownload());
469  setModelData(row, PeerListColumns::TOT_DOWN, totalDown, peer.totalDownload(), intDataTextAlignment);
470  const QString totalUp = (hideValues && (peer.totalUpload() <= 0)) ? QString {} : Utils::Misc::friendlyUnit(peer.totalUpload());
471  setModelData(row, PeerListColumns::TOT_UP, totalUp, peer.totalUpload(), intDataTextAlignment);
472  setModelData(row, PeerListColumns::RELEVANCE, (Utils::String::fromDouble(peer.relevance() * 100, 1) + '%'), peer.relevance(), intDataTextAlignment);
473 
474  const QStringList downloadingFiles {torrent->hasMetadata()
475  ? torrent->info().filesForPiece(peer.downloadingPieceIndex())
476  : QStringList()};
477  const QString downloadingFilesDisplayValue = downloadingFiles.join(';');
478  setModelData(row, PeerListColumns::DOWNLOADING_PIECE, downloadingFilesDisplayValue, downloadingFilesDisplayValue, {}, downloadingFiles.join(QLatin1Char('\n')));
479 
480  if (m_resolver)
481  m_resolver->resolve(peerEndpoint.address.ip);
482 
483  if (m_resolveCountries)
484  {
485  const QIcon icon = UIThemeManager::instance()->getFlagIcon(peer.country());
486  if (!icon.isNull())
487  {
488  m_listModel->setData(m_listModel->index(row, PeerListColumns::COUNTRY), icon, Qt::DecorationRole);
489  const QString countryName = Net::GeoIPManager::CountryName(peer.country());
490  m_listModel->setData(m_listModel->index(row, PeerListColumns::COUNTRY), countryName, Qt::ToolTipRole);
491  }
492  }
493 }
494 
495 void PeerListWidget::handleResolved(const QHostAddress &ip, const QString &hostname) const
496 {
497  if (hostname.isEmpty())
498  return;
499 
500  const QSet<QStandardItem *> items = m_itemsByIP.value(ip);
501  for (QStandardItem *item : items)
502  item->setData(hostname, Qt::DisplayRole);
503 }
504 
506 {
507  if (col == PeerListColumns::COUNTRY)
508  m_proxyModel->setSortRole(Qt::ToolTipRole);
509  else
511 }
512 
513 void PeerListWidget::wheelEvent(QWheelEvent *event)
514 {
515  if (event->modifiers() & Qt::ShiftModifier)
516  {
517  // Shift + scroll = horizontal scroll
518  event->accept();
519  QWheelEvent scrollHEvent(event->position(), event->globalPosition()
520  , event->pixelDelta(), event->angleDelta().transposed(), event->buttons()
521  , event->modifiers(), event->phase(), event->inverted(), event->source());
522  QTreeView::wheelEvent(&scrollHEvent);
523  return;
524  }
525 
526  QTreeView::wheelEvent(event); // event delegated to base class
527 }
qlonglong totalUpload() const
Definition: peerinfo.cpp:198
qlonglong totalDownload() const
Definition: peerinfo.cpp:203
QString flagsDescription() const
Definition: peerinfo.cpp:347
int payloadUpSpeed() const
Definition: peerinfo.cpp:188
int payloadDownSpeed() const
Definition: peerinfo.cpp:193
QString flags() const
Definition: peerinfo.cpp:342
QString client() const
Definition: peerinfo.cpp:178
QString connectionType() const
Definition: peerinfo.cpp:219
PeerAddress address() const
Definition: peerinfo.cpp:169
qreal relevance() const
Definition: peerinfo.cpp:253
int downloadingPieceIndex() const
Definition: peerinfo.cpp:352
QString country() const
Definition: peerinfo.cpp:62
qreal progress() const
Definition: peerinfo.cpp:183
static Session * instance()
Definition: session.cpp:997
void banIP(const QString &ip)
Definition: session.cpp:1771
virtual QVector< PeerInfo > peers() const =0
virtual bool isChecking() const =0
virtual bool isQueued() const =0
virtual bool isPrivate() const =0
virtual TorrentInfo info() const =0
virtual bool hasMetadata() const =0
QStringList filesForPiece(int pieceIndex) const
static QString CountryName(const QString &countryISOCode)
void ipResolved(const QHostAddress &ip, const QString &hostname)
void resolve(const QHostAddress &ip)
Net::ReverseResolution * m_resolver
void wheelEvent(QWheelEvent *event) override
void displayToggleColumnsMenu(const QPoint &)
QHash< QHostAddress, QSet< QStandardItem * > > m_itemsByIP
void handleSortColumnChanged(int col)
PropertiesWidget * m_properties
PeerListSortModel * m_proxyModel
QHash< PeerEndpoint, QStandardItem * > m_peerItems
PeerListWidget(PropertiesWidget *parent)
void updatePeerCountryResolutionState()
void loadPeers(const BitTorrent::Torrent *torrent)
QStandardItemModel * m_listModel
~PeerListWidget() override
void updatePeer(const BitTorrent::Torrent *torrent, const BitTorrent::PeerInfo &peer, bool &isNewPeer)
void saveSettings() const
void updatePeerHostNameResolutionState()
void handleResolved(const QHostAddress &ip, const QString &hostname) const
void showPeerListMenu(const QPoint &)
static QVector< BitTorrent::PeerAddress > askForPeers(QWidget *parent)
bool resolvePeerCountries() const
static Preferences * instance()
void setPeerListState(const QByteArray &state)
bool getHideZeroValues() const
BitTorrent::Torrent * getCurrentTorrent() const
static UIThemeManager * instance()
QIcon getFlagIcon(const QString &countryIsoCode) const
QIcon getIcon(const QString &iconId, const QString &fallback={}) const
constexpr std::add_const_t< T > & asConst(T &t) noexcept
Definition: global.h:42
void LogMsg(const QString &message, const Log::MsgType &type)
Definition: logger.cpp:125
QString friendlyUnit(qint64 bytes, bool isSpeed=false)
Definition: misc.cpp:261
QString fromDouble(double n, int precision)
Definition: string.cpp:44
action
Definition: tstool.py:143
uint qHash(const PeerEndpoint &peerEndpoint, const uint seed)
bool operator==(const PeerEndpoint &left, const PeerEndpoint &right)
@ PROGRESS
BitTorrent::PeerAddress address
QString connectionType