peertablemodel.cpp
1 // Copyright (c) 2011-2022 The Bitcoin Core developers 2 // Distributed under the MIT software license, see the accompanying 3 // file COPYING or http://www.opensource.org/licenses/mit-license.php. 4 5 #include <qt/peertablemodel.h> 6 7 #include <qt/guiconstants.h> 8 #include <qt/guiutil.h> 9 10 #include <interfaces/node.h> 11 12 #include <utility> 13 14 #include <QList> 15 #include <QTimer> 16 17 PeerTableModel::PeerTableModel(interfaces::Node& node, QObject* parent) 18 : QAbstractTableModel(parent), 19 m_node(node) 20 { 21 // set up timer for auto refresh 22 timer = new QTimer(this); 23 connect(timer, &QTimer::timeout, this, &PeerTableModel::refresh); 24 timer->setInterval(MODEL_UPDATE_DELAY); 25 26 // load initial data 27 refresh(); 28 } 29 30 PeerTableModel::~PeerTableModel() = default; 31 32 void PeerTableModel::startAutoRefresh() 33 { 34 timer->start(); 35 } 36 37 void PeerTableModel::stopAutoRefresh() 38 { 39 timer->stop(); 40 } 41 42 int PeerTableModel::rowCount(const QModelIndex& parent) const 43 { 44 if (parent.isValid()) { 45 return 0; 46 } 47 return m_peers_data.size(); 48 } 49 50 int PeerTableModel::columnCount(const QModelIndex& parent) const 51 { 52 if (parent.isValid()) { 53 return 0; 54 } 55 return columns.length(); 56 } 57 58 QVariant PeerTableModel::data(const QModelIndex& index, int role) const 59 { 60 if(!index.isValid()) 61 return QVariant(); 62 63 CNodeCombinedStats *rec = static_cast<CNodeCombinedStats*>(index.internalPointer()); 64 65 const auto column = static_cast<ColumnIndex>(index.column()); 66 if (role == Qt::DisplayRole) { 67 switch (column) { 68 case NetNodeId: 69 return (qint64)rec->nodeStats.nodeid; 70 case Age: 71 return GUIUtil::FormatPeerAge(rec->nodeStats.m_connected); 72 case Address: 73 return QString::fromStdString(rec->nodeStats.m_addr_name); 74 case Direction: 75 return QString(rec->nodeStats.fInbound ? 76 //: An Inbound Connection from a Peer. 77 tr("Inbound") : 78 //: An Outbound Connection to a Peer. 79 tr("Outbound")); 80 case ConnectionType: 81 return GUIUtil::ConnectionTypeToQString(rec->nodeStats.m_conn_type, /*prepend_direction=*/false); 82 case Network: 83 return GUIUtil::NetworkToQString(rec->nodeStats.m_network); 84 case Ping: 85 return GUIUtil::formatPingTime(rec->nodeStats.m_min_ping_time); 86 case Sent: 87 return GUIUtil::formatBytes(rec->nodeStats.nSendBytes); 88 case Received: 89 return GUIUtil::formatBytes(rec->nodeStats.nRecvBytes); 90 case Subversion: 91 return QString::fromStdString(rec->nodeStats.cleanSubVer); 92 } // no default case, so the compiler can warn about missing cases 93 assert(false); 94 } else if (role == Qt::TextAlignmentRole) { 95 switch (column) { 96 case NetNodeId: 97 case Age: 98 return QVariant(Qt::AlignRight | Qt::AlignVCenter); 99 case Address: 100 return {}; 101 case Direction: 102 case ConnectionType: 103 case Network: 104 return QVariant(Qt::AlignCenter); 105 case Ping: 106 case Sent: 107 case Received: 108 return QVariant(Qt::AlignRight | Qt::AlignVCenter); 109 case Subversion: 110 return {}; 111 } // no default case, so the compiler can warn about missing cases 112 assert(false); 113 } else if (role == StatsRole) { 114 return QVariant::fromValue(rec); 115 } 116 117 return QVariant(); 118 } 119 120 QVariant PeerTableModel::headerData(int section, Qt::Orientation orientation, int role) const 121 { 122 if(orientation == Qt::Horizontal) 123 { 124 if(role == Qt::DisplayRole && section < columns.size()) 125 { 126 return columns[section]; 127 } 128 } 129 return QVariant(); 130 } 131 132 Qt::ItemFlags PeerTableModel::flags(const QModelIndex &index) const 133 { 134 if (!index.isValid()) return Qt::NoItemFlags; 135 136 Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled; 137 return retval; 138 } 139 140 QModelIndex PeerTableModel::index(int row, int column, const QModelIndex& parent) const 141 { 142 Q_UNUSED(parent); 143 144 if (0 <= row && row < rowCount() && 0 <= column && column < columnCount()) { 145 return createIndex(row, column, const_cast<CNodeCombinedStats*>(&m_peers_data[row])); 146 } 147 148 return QModelIndex(); 149 } 150 151 void PeerTableModel::refresh() 152 { 153 interfaces::Node::NodesStats nodes_stats; 154 m_node.getNodesStats(nodes_stats); 155 decltype(m_peers_data) new_peers_data; 156 new_peers_data.reserve(nodes_stats.size()); 157 for (const auto& node_stats : nodes_stats) { 158 const CNodeCombinedStats stats{std::get<0>(node_stats), std::get<2>(node_stats), std::get<1>(node_stats)}; 159 new_peers_data.append(stats); 160 } 161 162 // Handle peer addition or removal as suggested in Qt Docs. See: 163 // - https://doc.qt.io/qt-5/model-view-programming.html#inserting-and-removing-rows 164 // - https://doc.qt.io/qt-5/model-view-programming.html#resizable-models 165 // We take advantage of the fact that the std::vector returned 166 // by interfaces::Node::getNodesStats is sorted by nodeid. 167 for (int i = 0; i < m_peers_data.size();) { 168 if (i < new_peers_data.size() && m_peers_data.at(i).nodeStats.nodeid == new_peers_data.at(i).nodeStats.nodeid) { 169 ++i; 170 continue; 171 } 172 // A peer has been removed from the table. 173 beginRemoveRows(QModelIndex(), i, i); 174 m_peers_data.erase(m_peers_data.begin() + i); 175 endRemoveRows(); 176 } 177 178 if (m_peers_data.size() < new_peers_data.size()) { 179 // Some peers have been added to the end of the table. 180 beginInsertRows(QModelIndex(), m_peers_data.size(), new_peers_data.size() - 1); 181 m_peers_data.swap(new_peers_data); 182 endInsertRows(); 183 } else { 184 m_peers_data.swap(new_peers_data); 185 } 186 187 const auto top_left = index(0, 0); 188 const auto bottom_right = index(rowCount() - 1, columnCount() - 1); 189 Q_EMIT dataChanged(top_left, bottom_right); 190 }