peertablemodel.cpp raw
1 // Copyright (c) 2011-present 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 }
191