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