2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If you
25 * modify file(s), you may extend this exception to your version of the file(s),
26 * but you are not obligated to do so. If you do not wish to do so, delete this
27 * exception statement from your version.
30 #include "peerlistwidget.h"
35 #include <QApplication>
37 #include <QHeaderView>
38 #include <QHostAddress>
40 #include <QMessageBox>
44 #include <QSortFilterProxyModel>
45 #include <QStandardItemModel>
47 #include <QWheelEvent>
49 #include "base/bittorrent/peeraddress.h"
50 #include "base/bittorrent/peerinfo.h"
51 #include "base/bittorrent/session.h"
52 #include "base/bittorrent/torrent.h"
53 #include "base/bittorrent/torrentinfo.h"
54 #include "base/global.h"
55 #include "base/logger.h"
56 #include "base/net/geoipmanager.h"
57 #include "base/net/reverseresolution.h"
58 #include "base/preferences.h"
59 #include "base/utils/misc.h"
60 #include "base/utils/string.h"
61 #include "gui/uithememanager.h"
62 #include "peerlistsortmodel.h"
63 #include "peersadditiondialog.h"
64 #include "propertieswidget.h"
68 BitTorrent::PeerAddress address
;
69 QString connectionType
; // matches return type of `PeerInfo::connectionType()`
72 bool operator==(const PeerEndpoint
&left
, const PeerEndpoint
&right
)
74 return (left
.address
== right
.address
) && (left
.connectionType
== right
.connectionType
);
77 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
78 std::size_t qHash(const PeerEndpoint
&peerEndpoint
, const std::size_t seed
= 0)
80 return qHashMulti(seed
, peerEndpoint
.address
, peerEndpoint
.connectionType
);
83 uint
qHash(const PeerEndpoint
&peerEndpoint
, const uint seed
= 0)
85 return (qHash(peerEndpoint
.address
, seed
) ^ ::qHash(peerEndpoint
.connectionType
));
91 void setModelData(QStandardItemModel
*model
, const int row
, const int column
, const QString
&displayData
92 , const QVariant
&underlyingData
, const Qt::Alignment textAlignmentData
= {}, const QString
&toolTip
= {})
94 const QMap
<int, QVariant
> data
= {
95 {Qt::DisplayRole
, displayData
},
96 {PeerListSortModel::UnderlyingDataRole
, underlyingData
},
97 {Qt::TextAlignmentRole
, QVariant
{textAlignmentData
}},
98 {Qt::ToolTipRole
, toolTip
}};
100 model
->setItemData(model
->index(row
, column
), data
);
104 PeerListWidget::PeerListWidget(PropertiesWidget
*parent
)
106 , m_properties(parent
)
109 const bool columnLoaded
= loadSettings();
111 setUniformRowHeights(true);
112 setRootIsDecorated(false);
113 setItemsExpandable(false);
114 setAllColumnsShowFocus(true);
115 setEditTriggers(QAbstractItemView::NoEditTriggers
);
116 setSelectionMode(QAbstractItemView::ExtendedSelection
);
117 header()->setFirstSectionMovable(true);
118 header()->setStretchLastSection(false);
119 header()->setTextElideMode(Qt::ElideRight
);
122 m_listModel
= new QStandardItemModel(0, PeerListColumns::COL_COUNT
, this);
123 m_listModel
->setHeaderData(PeerListColumns::COUNTRY
, Qt::Horizontal
, tr("Country/Region")); // Country flag column
124 m_listModel
->setHeaderData(PeerListColumns::IP
, Qt::Horizontal
, tr("IP"));
125 m_listModel
->setHeaderData(PeerListColumns::PORT
, Qt::Horizontal
, tr("Port"));
126 m_listModel
->setHeaderData(PeerListColumns::FLAGS
, Qt::Horizontal
, tr("Flags"));
127 m_listModel
->setHeaderData(PeerListColumns::CONNECTION
, Qt::Horizontal
, tr("Connection"));
128 m_listModel
->setHeaderData(PeerListColumns::CLIENT
, Qt::Horizontal
, tr("Client", "i.e.: Client application"));
129 m_listModel
->setHeaderData(PeerListColumns::PEERID_CLIENT
, Qt::Horizontal
, tr("Peer ID Client", "i.e.: Client resolved from Peer ID"));
130 m_listModel
->setHeaderData(PeerListColumns::PROGRESS
, Qt::Horizontal
, tr("Progress", "i.e: % downloaded"));
131 m_listModel
->setHeaderData(PeerListColumns::DOWN_SPEED
, Qt::Horizontal
, tr("Down Speed", "i.e: Download speed"));
132 m_listModel
->setHeaderData(PeerListColumns::UP_SPEED
, Qt::Horizontal
, tr("Up Speed", "i.e: Upload speed"));
133 m_listModel
->setHeaderData(PeerListColumns::TOT_DOWN
, Qt::Horizontal
, tr("Downloaded", "i.e: total data downloaded"));
134 m_listModel
->setHeaderData(PeerListColumns::TOT_UP
, Qt::Horizontal
, tr("Uploaded", "i.e: total data uploaded"));
135 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."));
136 m_listModel
->setHeaderData(PeerListColumns::DOWNLOADING_PIECE
, Qt::Horizontal
, tr("Files", "i.e. files that are being downloaded right now"));
137 // Set header text alignment
138 m_listModel
->setHeaderData(PeerListColumns::PORT
, Qt::Horizontal
, QVariant(Qt::AlignRight
| Qt::AlignVCenter
), Qt::TextAlignmentRole
);
139 m_listModel
->setHeaderData(PeerListColumns::PROGRESS
, Qt::Horizontal
, QVariant(Qt::AlignRight
| Qt::AlignVCenter
), Qt::TextAlignmentRole
);
140 m_listModel
->setHeaderData(PeerListColumns::DOWN_SPEED
, Qt::Horizontal
, QVariant(Qt::AlignRight
| Qt::AlignVCenter
), Qt::TextAlignmentRole
);
141 m_listModel
->setHeaderData(PeerListColumns::UP_SPEED
, Qt::Horizontal
, QVariant(Qt::AlignRight
| Qt::AlignVCenter
), Qt::TextAlignmentRole
);
142 m_listModel
->setHeaderData(PeerListColumns::TOT_DOWN
, Qt::Horizontal
, QVariant(Qt::AlignRight
| Qt::AlignVCenter
), Qt::TextAlignmentRole
);
143 m_listModel
->setHeaderData(PeerListColumns::TOT_UP
, Qt::Horizontal
, QVariant(Qt::AlignRight
| Qt::AlignVCenter
), Qt::TextAlignmentRole
);
144 m_listModel
->setHeaderData(PeerListColumns::RELEVANCE
, Qt::Horizontal
, QVariant(Qt::AlignRight
| Qt::AlignVCenter
), Qt::TextAlignmentRole
);
145 // Proxy model to support sorting without actually altering the underlying model
146 m_proxyModel
= new PeerListSortModel(this);
147 m_proxyModel
->setDynamicSortFilter(true);
148 m_proxyModel
->setSourceModel(m_listModel
);
149 m_proxyModel
->setSortCaseSensitivity(Qt::CaseInsensitive
);
150 setModel(m_proxyModel
);
152 hideColumn(PeerListColumns::IP_HIDDEN
);
153 hideColumn(PeerListColumns::COL_COUNT
);
155 // Default hidden columns
158 hideColumn(PeerListColumns::PEERID_CLIENT
);
161 m_resolveCountries
= Preferences::instance()->resolvePeerCountries();
162 if (!m_resolveCountries
)
163 hideColumn(PeerListColumns::COUNTRY
);
164 // Ensure that at least one column is visible at all times
165 bool atLeastOne
= false;
166 for (int i
= 0; i
< PeerListColumns::IP_HIDDEN
; ++i
)
168 if (!isColumnHidden(i
))
175 setColumnHidden(PeerListColumns::IP
, false);
176 // To also mitigate the above issue, we have to resize each column when
177 // its size is 0, because explicitly 'showing' the column isn't enough
178 // in the above scenario.
179 for (int i
= 0; i
< PeerListColumns::IP_HIDDEN
; ++i
)
181 if ((columnWidth(i
) <= 0) && !isColumnHidden(i
))
182 resizeColumnToContents(i
);
185 setContextMenuPolicy(Qt::CustomContextMenu
);
186 connect(this, &QWidget::customContextMenuRequested
, this, &PeerListWidget::showPeerListMenu
);
188 setSortingEnabled(true);
189 // IP to Hostname resolver
190 updatePeerHostNameResolutionState();
192 header()->setContextMenuPolicy(Qt::CustomContextMenu
);
193 connect(header(), &QWidget::customContextMenuRequested
, this, &PeerListWidget::displayColumnHeaderMenu
);
194 connect(header(), &QHeaderView::sectionClicked
, this, &PeerListWidget::handleSortColumnChanged
);
195 connect(header(), &QHeaderView::sectionMoved
, this, &PeerListWidget::saveSettings
);
196 connect(header(), &QHeaderView::sectionResized
, this, &PeerListWidget::saveSettings
);
197 connect(header(), &QHeaderView::sortIndicatorChanged
, this, &PeerListWidget::saveSettings
);
198 handleSortColumnChanged(header()->sortIndicatorSection());
199 const auto *copyHotkey
= new QShortcut(QKeySequence::Copy
, this, nullptr, nullptr, Qt::WidgetShortcut
);
200 connect(copyHotkey
, &QShortcut::activated
, this, &PeerListWidget::copySelectedPeers
);
201 const auto *deleteHotkey
= new QShortcut(QKeySequence::Delete
, this, nullptr, nullptr, Qt::WidgetShortcut
);
202 connect(deleteHotkey
, &QShortcut::activated
, this, &PeerListWidget::banSelectedPeers
);
205 PeerListWidget::~PeerListWidget()
210 void PeerListWidget::displayColumnHeaderMenu()
212 QMenu
*menu
= new QMenu(this);
213 menu
->setAttribute(Qt::WA_DeleteOnClose
);
214 menu
->setTitle(tr("Column visibility"));
215 menu
->setToolTipsVisible(true);
217 for (int i
= 0; i
< PeerListColumns::IP_HIDDEN
; ++i
)
219 if ((i
== PeerListColumns::COUNTRY
) && !Preferences::instance()->resolvePeerCountries())
222 const auto columnName
= m_listModel
->headerData(i
, Qt::Horizontal
, Qt::DisplayRole
).toString();
223 QAction
*action
= menu
->addAction(columnName
, this, [this, i
](const bool checked
)
225 if (!checked
&& (visibleColumnsCount() <= 1))
228 setColumnHidden(i
, !checked
);
230 if (checked
&& (columnWidth(i
) <= 5))
231 resizeColumnToContents(i
);
235 action
->setCheckable(true);
236 action
->setChecked(!isColumnHidden(i
));
239 menu
->addSeparator();
240 QAction
*resizeAction
= menu
->addAction(tr("Resize columns"), this, [this]()
242 for (int i
= 0, count
= header()->count(); i
< count
; ++i
)
244 if (!isColumnHidden(i
))
245 resizeColumnToContents(i
);
249 resizeAction
->setToolTip(tr("Resize all non-hidden columns to the size of their contents"));
251 menu
->popup(QCursor::pos());
254 void PeerListWidget::updatePeerHostNameResolutionState()
256 if (Preferences::instance()->resolvePeerHostNames())
260 m_resolver
= new Net::ReverseResolution(this);
261 connect(m_resolver
, &Net::ReverseResolution::ipResolved
, this, &PeerListWidget::handleResolved
);
262 loadPeers(m_properties
->getCurrentTorrent());
268 m_resolver
= nullptr;
272 void PeerListWidget::updatePeerCountryResolutionState()
274 const bool resolveCountries
= Preferences::instance()->resolvePeerCountries();
275 if (resolveCountries
== m_resolveCountries
)
278 m_resolveCountries
= resolveCountries
;
279 if (m_resolveCountries
)
281 loadPeers(m_properties
->getCurrentTorrent());
282 showColumn(PeerListColumns::COUNTRY
);
283 if (columnWidth(PeerListColumns::COUNTRY
) <= 0)
284 resizeColumnToContents(PeerListColumns::COUNTRY
);
288 hideColumn(PeerListColumns::COUNTRY
);
292 void PeerListWidget::showPeerListMenu()
294 BitTorrent::Torrent
*torrent
= m_properties
->getCurrentTorrent();
295 if (!torrent
) return;
297 auto *menu
= new QMenu(this);
298 menu
->setAttribute(Qt::WA_DeleteOnClose
);
299 menu
->setToolTipsVisible(true);
301 QAction
*addNewPeer
= menu
->addAction(UIThemeManager::instance()->getIcon(u
"peers-add"_qs
), tr("Add peers...")
302 , this, [this, torrent
]()
304 const QVector
<BitTorrent::PeerAddress
> peersList
= PeersAdditionDialog::askForPeers(this);
305 const int peerCount
= std::count_if(peersList
.cbegin(), peersList
.cend(), [torrent
](const BitTorrent::PeerAddress
&peer
)
307 return torrent
->connectPeer(peer
);
309 if (peerCount
< peersList
.length())
310 QMessageBox::information(this, tr("Adding peers"), tr("Some peers cannot be added. Check the Log for details."));
311 else if (peerCount
> 0)
312 QMessageBox::information(this, tr("Adding peers"), tr("Peers are added to this torrent."));
314 QAction
*copyPeers
= menu
->addAction(UIThemeManager::instance()->getIcon(u
"edit-copy"_qs
), tr("Copy IP:port")
315 , this, &PeerListWidget::copySelectedPeers
);
316 menu
->addSeparator();
317 QAction
*banPeers
= menu
->addAction(UIThemeManager::instance()->getIcon(u
"peers-remove"_qs
), tr("Ban peer permanently")
318 , this, &PeerListWidget::banSelectedPeers
);
321 const auto disableAction
= [](QAction
*action
, const QString
&tooltip
)
323 action
->setEnabled(false);
324 action
->setToolTip(tooltip
);
327 if (torrent
->isPrivate())
328 disableAction(addNewPeer
, tr("Cannot add peers to a private torrent"));
329 else if (torrent
->isChecking())
330 disableAction(addNewPeer
, tr("Cannot add peers when the torrent is checking"));
331 else if (torrent
->isQueued())
332 disableAction(addNewPeer
, tr("Cannot add peers when the torrent is queued"));
334 if (selectionModel()->selectedRows().isEmpty())
336 const QString tooltip
= tr("No peer was selected");
337 disableAction(copyPeers
, tooltip
);
338 disableAction(banPeers
, tooltip
);
341 menu
->popup(QCursor::pos());
344 void PeerListWidget::banSelectedPeers()
346 // Store selected rows first as selected peers may disconnect
347 const QModelIndexList selectedIndexes
= selectionModel()->selectedRows();
349 QVector
<QString
> selectedIPs
;
350 selectedIPs
.reserve(selectedIndexes
.size());
352 for (const QModelIndex
&index
: selectedIndexes
)
354 const int row
= m_proxyModel
->mapToSource(index
).row();
355 const QString ip
= m_listModel
->item(row
, PeerListColumns::IP_HIDDEN
)->text();
359 // Confirm before banning peer
360 const QMessageBox::StandardButton btn
= QMessageBox::question(this, tr("Ban peer permanently")
361 , tr("Are you sure you want to permanently ban the selected peers?"));
362 if (btn
!= QMessageBox::Yes
) return;
364 for (const QString
&ip
: selectedIPs
)
366 BitTorrent::Session::instance()->banIP(ip
);
367 LogMsg(tr("Peer \"%1\" is manually banned").arg(ip
));
370 loadPeers(m_properties
->getCurrentTorrent());
373 void PeerListWidget::copySelectedPeers()
375 const QModelIndexList selectedIndexes
= selectionModel()->selectedRows();
376 QStringList selectedPeers
;
378 for (const QModelIndex
&index
: selectedIndexes
)
380 const int row
= m_proxyModel
->mapToSource(index
).row();
381 const QString ip
= m_listModel
->item(row
, PeerListColumns::IP_HIDDEN
)->text();
382 const QString port
= m_listModel
->item(row
, PeerListColumns::PORT
)->text();
384 if (!ip
.contains(u
'.')) // IPv6
385 selectedPeers
<< (u
'[' + ip
+ u
"]:" + port
);
387 selectedPeers
<< (ip
+ u
':' + port
);
390 QApplication::clipboard()->setText(selectedPeers
.join(u
'\n'));
393 void PeerListWidget::clear()
397 const int nbrows
= m_listModel
->rowCount();
399 m_listModel
->removeRows(0, nbrows
);
402 bool PeerListWidget::loadSettings()
404 return header()->restoreState(Preferences::instance()->getPeerListState());
407 void PeerListWidget::saveSettings() const
409 Preferences::instance()->setPeerListState(header()->saveState());
412 void PeerListWidget::loadPeers(const BitTorrent::Torrent
*torrent
)
417 using TorrentPtr
= QPointer
<const BitTorrent::Torrent
>;
418 torrent
->fetchPeerInfo([this, torrent
= TorrentPtr(torrent
)](const QVector
<BitTorrent::PeerInfo
> &peers
)
420 if (torrent
!= m_properties
->getCurrentTorrent())
423 // Remove I2P peers since they will be completely reloaded.
424 for (QStandardItem
*item
: asConst(m_I2PPeerItems
))
425 m_listModel
->removeRow(item
->row());
426 m_I2PPeerItems
.clear();
428 QSet
<PeerEndpoint
> existingPeers
;
429 existingPeers
.reserve(m_peerItems
.size());
430 for (auto i
= m_peerItems
.cbegin(); i
!= m_peerItems
.cend(); ++i
)
431 existingPeers
.insert(i
.key());
433 const bool hideZeroValues
= Preferences::instance()->getHideZeroValues();
434 for (const BitTorrent::PeerInfo
&peer
: peers
)
436 if (peer
.address().ip
.isNull())
439 const PeerEndpoint peerEndpoint
{peer
.address(), peer
.connectionType()};
441 auto itemIter
= m_peerItems
.find(peerEndpoint
);
442 const bool isNewPeer
= (itemIter
== m_peerItems
.end());
443 const int row
= isNewPeer
? m_listModel
->rowCount() : (*itemIter
)->row();
446 m_listModel
->insertRow(row
);
448 const bool useI2PSocket
= peer
.useI2PSocket();
450 const QString peerIPString
= useI2PSocket
? tr("N/A") : peerEndpoint
.address
.ip
.toString();
451 setModelData(m_listModel
, row
, PeerListColumns::IP
, peerIPString
, peerIPString
, {}, peerIPString
);
453 const QString peerIPHiddenString
= useI2PSocket
? QString() : peerEndpoint
.address
.ip
.toString();
454 setModelData(m_listModel
, row
, PeerListColumns::IP_HIDDEN
, peerIPHiddenString
, peerIPHiddenString
);
456 const QString peerPortString
= useI2PSocket
? tr("N/A") : QString::number(peer
.address().port
);
457 setModelData(m_listModel
, row
, PeerListColumns::PORT
, peerPortString
, peer
.address().port
, (Qt::AlignRight
| Qt::AlignVCenter
));
461 m_I2PPeerItems
.append(m_listModel
->item(row
, PeerListColumns::IP
));
465 itemIter
= m_peerItems
.insert(peerEndpoint
, m_listModel
->item(row
, PeerListColumns::IP
));
466 m_itemsByIP
[peerEndpoint
.address
.ip
].insert(itemIter
.value());
471 existingPeers
.remove(peerEndpoint
);
474 updatePeer(row
, torrent
, peer
, hideZeroValues
);
477 // Remove peers that are gone
478 for (const PeerEndpoint
&peerEndpoint
: asConst(existingPeers
))
480 QStandardItem
*item
= m_peerItems
.take(peerEndpoint
);
482 QSet
<QStandardItem
*> &items
= m_itemsByIP
[peerEndpoint
.address
.ip
];
485 m_itemsByIP
.remove(peerEndpoint
.address
.ip
);
487 m_listModel
->removeRow(item
->row());
492 void PeerListWidget::updatePeer(const int row
, const BitTorrent::Torrent
*torrent
, const BitTorrent::PeerInfo
&peer
, const bool hideZeroValues
)
494 const Qt::Alignment intDataTextAlignment
= Qt::AlignRight
| Qt::AlignVCenter
;
496 const QString client
= peer
.client().toHtmlEscaped();
497 setModelData(m_listModel
, row
, PeerListColumns::CLIENT
, client
, client
, {}, client
);
499 const QString peerIdClient
= peer
.peerIdClient().toHtmlEscaped();
500 setModelData(m_listModel
, row
, PeerListColumns::PEERID_CLIENT
, peerIdClient
, peerIdClient
);
502 const QString downSpeed
= (hideZeroValues
&& (peer
.payloadDownSpeed() <= 0))
503 ? QString() : Utils::Misc::friendlyUnit(peer
.payloadDownSpeed(), true);
504 setModelData(m_listModel
, row
, PeerListColumns::DOWN_SPEED
, downSpeed
, peer
.payloadDownSpeed(), intDataTextAlignment
);
506 const QString upSpeed
= (hideZeroValues
&& (peer
.payloadUpSpeed() <= 0))
507 ? QString() : Utils::Misc::friendlyUnit(peer
.payloadUpSpeed(), true);
508 setModelData(m_listModel
, row
, PeerListColumns::UP_SPEED
, upSpeed
, peer
.payloadUpSpeed(), intDataTextAlignment
);
510 const QString totalDown
= (hideZeroValues
&& (peer
.totalDownload() <= 0))
511 ? QString() : Utils::Misc::friendlyUnit(peer
.totalDownload());
512 setModelData(m_listModel
, row
, PeerListColumns::TOT_DOWN
, totalDown
, peer
.totalDownload(), intDataTextAlignment
);
514 const QString totalUp
= (hideZeroValues
&& (peer
.totalUpload() <= 0))
515 ? QString() : Utils::Misc::friendlyUnit(peer
.totalUpload());
516 setModelData(m_listModel
, row
, PeerListColumns::TOT_UP
, totalUp
, peer
.totalUpload(), intDataTextAlignment
);
518 setModelData(m_listModel
, row
, PeerListColumns::CONNECTION
, peer
.connectionType(), peer
.connectionType());
519 setModelData(m_listModel
, row
, PeerListColumns::FLAGS
, peer
.flags(), peer
.flags(), {}, peer
.flagsDescription());
520 setModelData(m_listModel
, row
, PeerListColumns::PROGRESS
, (Utils::String::fromDouble(peer
.progress() * 100, 1) + u
'%')
521 , peer
.progress(), intDataTextAlignment
);
522 setModelData(m_listModel
, row
, PeerListColumns::RELEVANCE
, (Utils::String::fromDouble(peer
.relevance() * 100, 1) + u
'%')
523 , peer
.relevance(), intDataTextAlignment
);
525 const PathList filePaths
= torrent
->info().filesForPiece(peer
.downloadingPieceIndex());
526 QStringList downloadingFiles
;
527 downloadingFiles
.reserve(filePaths
.size());
528 for (const Path
&filePath
: filePaths
)
529 downloadingFiles
.append(filePath
.toString());
531 const QString downloadingFilesDisplayValue
= downloadingFiles
.join(u
';');
532 setModelData(m_listModel
, row
, PeerListColumns::DOWNLOADING_PIECE
, downloadingFilesDisplayValue
533 , downloadingFilesDisplayValue
, {}, downloadingFiles
.join(u
'\n'));
535 if (!peer
.useI2PSocket() && m_resolver
)
536 m_resolver
->resolve(peer
.address().ip
);
538 if (m_resolveCountries
)
540 const QIcon icon
= UIThemeManager::instance()->getFlagIcon(peer
.country());
543 m_listModel
->setData(m_listModel
->index(row
, PeerListColumns::COUNTRY
), icon
, Qt::DecorationRole
);
544 const QString countryName
= Net::GeoIPManager::CountryName(peer
.country());
545 m_listModel
->setData(m_listModel
->index(row
, PeerListColumns::COUNTRY
), countryName
, Qt::ToolTipRole
);
550 int PeerListWidget::visibleColumnsCount() const
553 for (int i
= 0, iMax
= header()->count(); i
< iMax
; ++i
)
555 if (!isColumnHidden(i
))
562 void PeerListWidget::handleResolved(const QHostAddress
&ip
, const QString
&hostname
) const
564 if (hostname
.isEmpty())
567 const QSet
<QStandardItem
*> items
= m_itemsByIP
.value(ip
);
568 for (QStandardItem
*item
: items
)
569 item
->setData(hostname
, Qt::DisplayRole
);
572 void PeerListWidget::handleSortColumnChanged(const int col
)
574 if (col
== PeerListColumns::COUNTRY
)
575 m_proxyModel
->setSortRole(Qt::ToolTipRole
);
577 m_proxyModel
->setSortRole(PeerListSortModel::UnderlyingDataRole
);
580 void PeerListWidget::wheelEvent(QWheelEvent
*event
)
582 if (event
->modifiers() & Qt::ShiftModifier
)
584 // Shift + scroll = horizontal scroll
586 QWheelEvent scrollHEvent
{event
->position(), event
->globalPosition()
587 , event
->pixelDelta(), event
->angleDelta().transposed(), event
->buttons()
588 , event
->modifiers(), event
->phase(), event
->inverted(), event
->source()};
589 QTreeView::wheelEvent(&scrollHEvent
);
593 QTreeView::wheelEvent(event
); // event delegated to base class