/ src / qt / peertablemodel.cpp
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  }