Use tray icon from system theme only if option is set
[qBittorrent.git] / src / gui / properties / peerlistwidget.cpp
blobe0b57eaeebba2d79877708a5c2736b6d4e82d78f
1 /*
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"
32 #include <algorithm>
34 #include <QtGlobal>
35 #include <QApplication>
36 #include <QClipboard>
37 #include <QHeaderView>
38 #include <QHostAddress>
39 #include <QMenu>
40 #include <QMessageBox>
41 #include <QPointer>
42 #include <QSet>
43 #include <QShortcut>
44 #include <QSortFilterProxyModel>
45 #include <QStandardItemModel>
46 #include <QVector>
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"
66 struct PeerEndpoint
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);
82 #else
83 uint qHash(const PeerEndpoint &peerEndpoint, const uint seed = 0)
85 return (qHash(peerEndpoint.address, seed) ^ ::qHash(peerEndpoint.connectionType));
87 #endif
89 namespace
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)
105 : QTreeView(parent)
106 , m_properties(parent)
108 // Load settings
109 const bool columnLoaded = loadSettings();
110 // Visual settings
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);
121 // List Model
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
156 if (!columnLoaded)
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))
170 atLeastOne = true;
171 break;
174 if (!atLeastOne)
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);
184 // Context menu
185 setContextMenuPolicy(Qt::CustomContextMenu);
186 connect(this, &QWidget::customContextMenuRequested, this, &PeerListWidget::showPeerListMenu);
187 // Enable sorting
188 setSortingEnabled(true);
189 // IP to Hostname resolver
190 updatePeerHostNameResolutionState();
191 // SIGNAL/SLOT
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()
207 saveSettings();
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())
220 continue;
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))
226 return;
228 setColumnHidden(i, !checked);
230 if (checked && (columnWidth(i) <= 5))
231 resizeColumnToContents(i);
233 saveSettings();
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);
247 saveSettings();
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())
258 if (!m_resolver)
260 m_resolver = new Net::ReverseResolution(this);
261 connect(m_resolver, &Net::ReverseResolution::ipResolved, this, &PeerListWidget::handleResolved);
262 loadPeers(m_properties->getCurrentTorrent());
265 else
267 delete m_resolver;
268 m_resolver = nullptr;
272 void PeerListWidget::updatePeerCountryResolutionState()
274 const bool resolveCountries = Preferences::instance()->resolvePeerCountries();
275 if (resolveCountries == m_resolveCountries)
276 return;
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);
286 else
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);
320 // disable actions
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();
356 selectedIPs += ip;
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));
369 // Refresh list
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);
386 else // IPv4
387 selectedPeers << (ip + u':' + port);
390 QApplication::clipboard()->setText(selectedPeers.join(u'\n'));
393 void PeerListWidget::clear()
395 m_peerItems.clear();
396 m_itemsByIP.clear();
397 const int nbrows = m_listModel->rowCount();
398 if (nbrows > 0)
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)
414 if (!torrent)
415 return;
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())
421 return;
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())
437 continue;
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();
444 if (isNewPeer)
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));
459 if (useI2PSocket)
461 m_I2PPeerItems.append(m_listModel->item(row, PeerListColumns::IP));
463 else
465 itemIter = m_peerItems.insert(peerEndpoint, m_listModel->item(row, PeerListColumns::IP));
466 m_itemsByIP[peerEndpoint.address.ip].insert(itemIter.value());
469 else
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];
483 items.remove(item);
484 if (items.isEmpty())
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());
541 if (!icon.isNull())
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
552 int count = 0;
553 for (int i = 0, iMax = header()->count(); i < iMax; ++i)
555 if (!isColumnHidden(i))
556 ++count;
559 return count;
562 void PeerListWidget::handleResolved(const QHostAddress &ip, const QString &hostname) const
564 if (hostname.isEmpty())
565 return;
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);
576 else
577 m_proxyModel->setSortRole(PeerListSortModel::UnderlyingDataRole);
580 void PeerListWidget::wheelEvent(QWheelEvent *event)
582 if (event->modifiers() & Qt::ShiftModifier)
584 // Shift + scroll = horizontal scroll
585 event->accept();
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);
590 return;
593 QTreeView::wheelEvent(event); // event delegated to base class