Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/qt/bitcoin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ void BitcoinApplication::initializeResult(bool success, interfaces::BlockAndHead
{
// Log this only after AppInitMain finishes, as then logging setup is guaranteed complete
qInfo() << "Platform customization:" << platformStyle->getName();
clientModel = new ClientModel(node(), optionsModel);
clientModel = new ClientModel(node(), optionsModel, *platformStyle);
window->setClientModel(clientModel, &tip_info);
#ifdef ENABLE_WALLET
if (WalletModel::isWalletEnabled()) {
Expand Down
4 changes: 2 additions & 2 deletions src/qt/clientmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
static int64_t nLastHeaderTipUpdateNotification = 0;
static int64_t nLastBlockTipUpdateNotification = 0;

ClientModel::ClientModel(interfaces::Node& node, OptionsModel *_optionsModel, QObject *parent) :
ClientModel::ClientModel(interfaces::Node& node, OptionsModel *_optionsModel, const PlatformStyle& platform_style, QObject *parent) :
QObject(parent),
m_node(node),
optionsModel(_optionsModel),
Expand All @@ -41,7 +41,7 @@ ClientModel::ClientModel(interfaces::Node& node, OptionsModel *_optionsModel, QO
cachedBestHeaderHeight = -1;
cachedBestHeaderTime = -1;

peerTableModel = new PeerTableModel(m_node, this);
peerTableModel = new PeerTableModel(m_node, platform_style, this);
m_peer_table_sort_proxy = new PeerTableSortProxy(this);
m_peer_table_sort_proxy->setSourceModel(peerTableModel);

Expand Down
3 changes: 2 additions & 1 deletion src/qt/clientmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class CBlockIndex;
class OptionsModel;
class PeerTableModel;
class PeerTableSortProxy;
class PlatformStyle;
enum class SynchronizationState;

namespace interfaces {
Expand Down Expand Up @@ -49,7 +50,7 @@ class ClientModel : public QObject
Q_OBJECT

public:
explicit ClientModel(interfaces::Node& node, OptionsModel *optionsModel, QObject *parent = nullptr);
explicit ClientModel(interfaces::Node& node, OptionsModel *optionsModel, const PlatformStyle&, QObject *parent = nullptr);
~ClientModel();

interfaces::Node& node() const { return m_node; }
Expand Down
127 changes: 120 additions & 7 deletions src/qt/peertablemodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,34 @@

#include <qt/guiconstants.h>
#include <qt/guiutil.h>
#include <qt/platformstyle.h>

#include <interfaces/node.h>

#include <utility>

#include <QBrush>
#include <QFont>
#include <QFontInfo>
#include <QImage>
#include <QPainter>
#include <QPixmap>
#include <QList>
#include <QTimer>

PeerTableModel::PeerTableModel(interfaces::Node& node, QObject* parent) :
PeerTableModel::PeerTableModel(interfaces::Node& node, const PlatformStyle& platform_style, QObject* parent) :
QAbstractTableModel(parent),
m_node(node),
m_platform_style(platform_style),
timer(nullptr)
{
// set up timer for auto refresh
timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &PeerTableModel::refresh);
timer->setInterval(MODEL_UPDATE_DELAY);

DrawIcons();

// load initial data
refresh();
}
Expand All @@ -33,6 +43,111 @@ PeerTableModel::~PeerTableModel()
// Intentionally left empty
}

void PeerTableModel::DrawIcons()
{
static constexpr auto SIZE = 32;
static constexpr auto ARROW_HEIGHT = SIZE * 2 / 3;
QImage icon_in(SIZE, SIZE, QImage::Format_Alpha8);
icon_in.fill(Qt::transparent);
QImage icon_out(icon_in);
QPainter icon_in_painter(&icon_in);
QPainter icon_out_painter(&icon_out);

// Arrow
auto DrawArrow = [](const int x, QPainter& icon_painter) {
icon_painter.setBrush(Qt::SolidPattern);
QPoint shape[] = {
{x, ARROW_HEIGHT / 2},
{(SIZE-1) - x, 0},
{(SIZE-1) - x, ARROW_HEIGHT-1},
};
icon_painter.drawConvexPolygon(shape, 3);
};
DrawArrow(0, icon_in_painter);
DrawArrow(SIZE-1, icon_out_painter);

{
//: Label on inbound connection icon
const QString label_in = tr("IN");
//: Label on outbound connection icon
const QString label_out = tr("OUT");
QImage scratch(SIZE, SIZE, QImage::Format_Alpha8);
QPainter scratch_painter(&scratch);
QFont font; // NOTE: Application default font
font.setBold(true);
auto CheckSize = [&](const QImage& icon, const QString& text, const bool align_right) {
// Make sure it's at least able to fit (width only)
if (scratch_painter.boundingRect(0, 0, SIZE, SIZE, 0, text).width() > SIZE) {
return false;
}

// Draw text on the scratch image
// NOTE: QImage::fill doesn't like QPainter being active
scratch_painter.setCompositionMode(QPainter::CompositionMode_Source);
scratch_painter.fillRect(0, 0, SIZE, SIZE, Qt::transparent);
scratch_painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
scratch_painter.drawText(0, SIZE, text);

int text_offset_x = 0;
if (align_right) {
// Figure out how far right we can shift it
for (int col = SIZE-1; col >= 0; --col) {
bool any_pixels = false;
for (int row = SIZE-1; row >= 0; --row) {
int opacity = qAlpha(scratch.pixel(col, row));
if (opacity > 0) {
any_pixels = true;
break;
}
}
if (any_pixels) {
text_offset_x = (SIZE-1) - col;
break;
}
}
}

// Check if there's any overlap
for (int row = 0; row < SIZE; ++row) {
for (int col = text_offset_x; col < SIZE; ++col) {
int opacity = qAlpha(icon.pixel(col, row));
if (col >= text_offset_x) {
opacity += qAlpha(scratch.pixel(col - text_offset_x, row));
}
if (opacity > 0xff) {
// Overlap found, we're done
return false;
}
}
}
return true;
};
int font_size = SIZE;
while (font_size > 1) {
font.setPixelSize(--font_size);
scratch_painter.setFont(font);
if (CheckSize(icon_in , label_in , /* align_right= */ false) &&
CheckSize(icon_out, label_out, /* align_right= */ true)) break;
}
icon_in_painter .drawText(0, 0, SIZE, SIZE, Qt::AlignLeft | Qt::AlignBottom, label_in);
icon_out_painter.drawText(0, 0, SIZE, SIZE, Qt::AlignRight | Qt::AlignBottom, label_out);
}
m_icon_conn_in = m_platform_style.TextColorIcon(QIcon(QPixmap::fromImage(icon_in)));
m_icon_conn_out = m_platform_style.TextColorIcon(QIcon(QPixmap::fromImage(icon_out)));
}

void PeerTableModel::updatePalette()
{
m_icon_conn_in = m_platform_style.TextColorIcon(m_icon_conn_in);
m_icon_conn_out = m_platform_style.TextColorIcon(m_icon_conn_out);
if (m_peers_data.empty()) return;
Q_EMIT dataChanged(
createIndex(0, Direction),
createIndex(m_peers_data.size() - 1, Direction),
QVector<int>{Qt::DecorationRole}
);
}

void PeerTableModel::startAutoRefresh()
{
timer->start();
Expand Down Expand Up @@ -76,11 +191,7 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const
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"));
return {};
case ConnectionType:
return GUIUtil::ConnectionTypeToQString(rec->nodeStats.m_conn_type, /*prepend_direction=*/false);
case Network:
Expand All @@ -99,10 +210,10 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const
switch (column) {
case NetNodeId:
case Age:
case Direction:
return QVariant(Qt::AlignRight | Qt::AlignVCenter);
case Address:
return {};
case Direction:
case ConnectionType:
case Network:
return QVariant(Qt::AlignCenter);
Expand All @@ -116,6 +227,8 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const
assert(false);
} else if (role == StatsRole) {
return QVariant::fromValue(rec);
} else if (index.column() == Direction && role == Qt::DecorationRole) {
return rec->nodeStats.fInbound ? m_icon_conn_in : m_icon_conn_out;
}

return QVariant();
Expand Down
15 changes: 10 additions & 5 deletions src/qt/peertablemodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
#include <net.h>

#include <QAbstractTableModel>
#include <QIcon>
#include <QList>
#include <QModelIndex>
#include <QStringList>
#include <QVariant>

class PeerTablePriv;
class PlatformStyle;

namespace interfaces {
class Node;
Expand All @@ -40,16 +42,17 @@ class PeerTableModel : public QAbstractTableModel
Q_OBJECT

public:
explicit PeerTableModel(interfaces::Node& node, QObject* parent);
explicit PeerTableModel(interfaces::Node& node, const PlatformStyle&, QObject* parent);
~PeerTableModel();
void startAutoRefresh();
void stopAutoRefresh();

// See also RPCConsole::ColumnWidths in rpcconsole.h
enum ColumnIndex {
NetNodeId = 0,
Age,
Address,
Direction,
Address,
ConnectionType,
Network,
Ping,
Expand All @@ -74,24 +77,26 @@ class PeerTableModel : public QAbstractTableModel

public Q_SLOTS:
void refresh();
void updatePalette();

private:
//! Internal peer data structure.
QList<CNodeCombinedStats> m_peers_data{};
interfaces::Node& m_node;
const PlatformStyle& m_platform_style;
void DrawIcons();
QIcon m_icon_conn_in, m_icon_conn_out;
const QStringList columns{
/*: Title of Peers Table column which contains a
unique number used to identify a connection. */
tr("Peer"),
/*: Title of Peers Table column which indicates the duration (length of time)
since the peer connection started. */
tr("Age"),
"", // Direction column has no title
/*: Title of Peers Table column which contains the
IP/Onion/I2P address of the connected peer. */
tr("Address"),
/*: Title of Peers Table column which indicates the direction
the peer connection was initiated from. */
tr("Direction"),
/*: Title of Peers Table column which describes the type of
peer connection. The "type" describes why the connection exists. */
tr("Type"),
Expand Down
6 changes: 6 additions & 0 deletions src/qt/rpcconsole.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -682,13 +682,15 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_
connect(model, &ClientModel::mempoolSizeChanged, this, &RPCConsole::setMempoolSize);

// set up peer table
clientModel->getPeerTableModel()->updatePalette();
ui->peerWidget->setModel(model->peerTableSortProxy());
ui->peerWidget->verticalHeader()->hide();
ui->peerWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
ui->peerWidget->setSelectionMode(QAbstractItemView::ExtendedSelection);
ui->peerWidget->setContextMenuPolicy(Qt::CustomContextMenu);

if (!ui->peerWidget->horizontalHeader()->restoreState(m_peer_widget_header_state)) {
ui->peerWidget->setColumnWidth(PeerTableModel::Direction, DIRECTION_COLUMN_WIDTH);
ui->peerWidget->setColumnWidth(PeerTableModel::Address, ADDRESS_COLUMN_WIDTH);
ui->peerWidget->setColumnWidth(PeerTableModel::Subversion, SUBVERSION_COLUMN_WIDTH);
ui->peerWidget->setColumnWidth(PeerTableModel::Ping, PING_COLUMN_WIDTH);
Expand Down Expand Up @@ -934,6 +936,10 @@ void RPCConsole::changeEvent(QEvent* e)
QUrl(ICON_MAPPING[i].url),
platformStyle->SingleColorImage(ICON_MAPPING[i].source).scaled(QSize(consoleFontSize * 2, consoleFontSize * 2), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
}

if (clientModel && clientModel->getPeerTableModel()) {
clientModel->getPeerTableModel()->updatePalette();
}
}

QWidget::changeEvent(e);
Expand Down
1 change: 1 addition & 0 deletions src/qt/rpcconsole.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ public Q_SLOTS:

enum ColumnWidths
{
DIRECTION_COLUMN_WIDTH = 32,
ADDRESS_COLUMN_WIDTH = 200,
SUBVERSION_COLUMN_WIDTH = 150,
PING_COLUMN_WIDTH = 80,
Expand Down
2 changes: 1 addition & 1 deletion src/qt/test/addressbooktests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ void TestAddAddressesToSendBook(interfaces::Node& node)
// Initialize relevant QT models.
std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other"));
OptionsModel optionsModel;
ClientModel clientModel(node, &optionsModel);
ClientModel clientModel(node, &optionsModel, *platformStyle);
WalletContext& context = *node.walletLoader().context();
AddWallet(context, wallet);
WalletModel walletModel(interfaces::MakeWallet(context, wallet), clientModel, platformStyle.get());
Expand Down
2 changes: 1 addition & 1 deletion src/qt/test/wallettests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ void TestGUI(interfaces::Node& node)
SendCoinsDialog sendCoinsDialog(platformStyle.get());
TransactionView transactionView(platformStyle.get());
OptionsModel optionsModel;
ClientModel clientModel(node, &optionsModel);
ClientModel clientModel(node, &optionsModel, *platformStyle);
WalletContext& context = *node.walletLoader().context();
AddWallet(context, wallet);
WalletModel walletModel(interfaces::MakeWallet(context, wallet), clientModel, platformStyle.get());
Expand Down