// Copyright (c) 2011-2021 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include PeerTableModel::PeerTableModel(interfaces::Node& node, QObject* parent) : QAbstractTableModel(parent), m_node(node), timer(nullptr) { // set up timer for auto refresh timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &PeerTableModel::refresh); timer->setInterval(MODEL_UPDATE_DELAY); // load initial data refresh(); } PeerTableModel::~PeerTableModel() = default; void PeerTableModel::startAutoRefresh() { timer->start(); } void PeerTableModel::stopAutoRefresh() { timer->stop(); } int PeerTableModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; } return m_peers_data.size(); } int PeerTableModel::columnCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; } return columns.length(); } QVariant PeerTableModel::data(const QModelIndex& index, int role) const { if(!index.isValid()) return QVariant(); CNodeCombinedStats *rec = static_cast(index.internalPointer()); const auto column = static_cast(index.column()); if (role == Qt::DisplayRole) { switch (column) { case NetNodeId: return (qint64)rec->nodeStats.nodeid; case Age: return GUIUtil::FormatPeerAge(rec->nodeStats.m_connected); case Address: return QString::fromStdString(rec->nodeStats.m_addr_name); case Direction: return QString(rec->nodeStats.fInbound ? //: An Inbound Connection from a Peer. tr("Inbound") : //: An Outbound Connection to a Peer. tr("Outbound")); case ConnectionType: return GUIUtil::ConnectionTypeToQString(rec->nodeStats.m_conn_type, /*prepend_direction=*/false); case Network: return GUIUtil::NetworkToQString(rec->nodeStats.m_network); case Ping: return GUIUtil::formatPingTime(rec->nodeStats.m_min_ping_time); case Sent: return GUIUtil::formatBytes(rec->nodeStats.nSendBytes); case Received: return GUIUtil::formatBytes(rec->nodeStats.nRecvBytes); case Subversion: return QString::fromStdString(rec->nodeStats.cleanSubVer); } // no default case, so the compiler can warn about missing cases assert(false); } else if (role == Qt::TextAlignmentRole) { switch (column) { case NetNodeId: case Age: return QVariant(Qt::AlignRight | Qt::AlignVCenter); case Address: return {}; case Direction: case ConnectionType: case Network: return QVariant(Qt::AlignCenter); case Ping: case Sent: case Received: return QVariant(Qt::AlignRight | Qt::AlignVCenter); case Subversion: return {}; } // no default case, so the compiler can warn about missing cases assert(false); } else if (role == StatsRole) { return QVariant::fromValue(rec); } return QVariant(); } QVariant PeerTableModel::headerData(int section, Qt::Orientation orientation, int role) const { if(orientation == Qt::Horizontal) { if(role == Qt::DisplayRole && section < columns.size()) { return columns[section]; } } return QVariant(); } Qt::ItemFlags PeerTableModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled; return retval; } QModelIndex PeerTableModel::index(int row, int column, const QModelIndex& parent) const { Q_UNUSED(parent); if (0 <= row && row < rowCount() && 0 <= column && column < columnCount()) { return createIndex(row, column, const_cast(&m_peers_data[row])); } return QModelIndex(); } void PeerTableModel::refresh() { interfaces::Node::NodesStats nodes_stats; m_node.getNodesStats(nodes_stats); decltype(m_peers_data) new_peers_data; new_peers_data.reserve(nodes_stats.size()); for (const auto& node_stats : nodes_stats) { const CNodeCombinedStats stats{std::get<0>(node_stats), std::get<2>(node_stats), std::get<1>(node_stats)}; new_peers_data.append(stats); } // Handle peer addition or removal as suggested in Qt Docs. See: // - https://doc.qt.io/qt-5/model-view-programming.html#inserting-and-removing-rows // - https://doc.qt.io/qt-5/model-view-programming.html#resizable-models // We take advantage of the fact that the std::vector returned // by interfaces::Node::getNodesStats is sorted by nodeid. for (int i = 0; i < m_peers_data.size();) { if (i < new_peers_data.size() && m_peers_data.at(i).nodeStats.nodeid == new_peers_data.at(i).nodeStats.nodeid) { ++i; continue; } // A peer has been removed from the table. beginRemoveRows(QModelIndex(), i, i); m_peers_data.erase(m_peers_data.begin() + i); endRemoveRows(); } if (m_peers_data.size() < new_peers_data.size()) { // Some peers have been added to the end of the table. beginInsertRows(QModelIndex(), m_peers_data.size(), new_peers_data.size() - 1); m_peers_data.swap(new_peers_data); endInsertRows(); } else { m_peers_data.swap(new_peers_data); } const auto top_left = index(0, 0); const auto bottom_right = index(rowCount() - 1, columnCount() - 1); Q_EMIT dataChanged(top_left, bottom_right); }