Skip to content

Commit bc2ffa1

Browse files
committed
GUI: Peers: Add direction table column before "Address" with arrow
Arrow icon drawn at startup to make text translatable
1 parent 872328e commit bc2ffa1

File tree

5 files changed

+117
-2
lines changed

5 files changed

+117
-2
lines changed

src/qt/peertablemodel.cpp

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212

1313
#include <utility>
1414

15+
#include <QBrush>
16+
#include <QFont>
17+
#include <QFontInfo>
18+
#include <QImage>
19+
#include <QPainter>
20+
#include <QPixmap>
1521
#include <QList>
1622
#include <QTimer>
1723

@@ -26,6 +32,8 @@ PeerTableModel::PeerTableModel(interfaces::Node& node, const PlatformStyle& plat
2632
connect(timer, &QTimer::timeout, this, &PeerTableModel::refresh);
2733
timer->setInterval(MODEL_UPDATE_DELAY);
2834

35+
DrawIcons();
36+
2937
// load initial data
3038
refresh();
3139
}
@@ -35,6 +43,99 @@ PeerTableModel::~PeerTableModel()
3543
// Intentionally left empty
3644
}
3745

46+
void PeerTableModel::DrawIcons()
47+
{
48+
static constexpr auto SIZE = 32;
49+
static constexpr auto ARROW_HEIGHT = SIZE * 2 / 3;
50+
QImage icon_in(SIZE, SIZE, QImage::Format_Alpha8);
51+
icon_in.fill(Qt::transparent);
52+
QImage icon_out(icon_in);
53+
QPainter icon_in_painter(&icon_in);
54+
QPainter icon_out_painter(&icon_out);
55+
56+
// Arrow
57+
auto DrawArrow = [](const int x, QPainter& icon_painter) {
58+
icon_painter.setBrush(Qt::SolidPattern);
59+
QPoint shape[] = {
60+
{x, ARROW_HEIGHT / 2},
61+
{(SIZE-1) - x, 0},
62+
{(SIZE-1) - x, ARROW_HEIGHT-1},
63+
};
64+
icon_painter.drawConvexPolygon(shape, 3);
65+
};
66+
DrawArrow(0, icon_in_painter);
67+
DrawArrow(SIZE-1, icon_out_painter);
68+
69+
{
70+
//: Label on inbound connection icon
71+
const QString label_in = tr("IN");
72+
//: Label on outbound connection icon
73+
const QString label_out = tr("OUT");
74+
QImage scratch(SIZE, SIZE, QImage::Format_Alpha8);
75+
QPainter scratch_painter(&scratch);
76+
QFont font; // NOTE: Application default font
77+
font.setBold(true);
78+
auto CheckSize = [&](const QImage& icon, const QString& text, const bool align_right) {
79+
// Make sure it's at least able to fit (width only)
80+
if (scratch_painter.boundingRect(0, 0, SIZE, SIZE, 0, text).width() > SIZE) {
81+
return false;
82+
}
83+
84+
// Draw text on the scratch image
85+
// NOTE: QImage::fill doesn't like QPainter being active
86+
scratch_painter.setCompositionMode(QPainter::CompositionMode_Source);
87+
scratch_painter.fillRect(0, 0, SIZE, SIZE, Qt::transparent);
88+
scratch_painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
89+
scratch_painter.drawText(0, SIZE, text);
90+
91+
int text_offset_x = 0;
92+
if (align_right) {
93+
// Figure out how far right we can shift it
94+
for (int col = SIZE-1; col >= 0; --col) {
95+
bool any_pixels = false;
96+
for (int row = SIZE-1; row >= 0; --row) {
97+
int opacity = qAlpha(scratch.pixel(col, row));
98+
if (opacity > 0) {
99+
any_pixels = true;
100+
break;
101+
}
102+
}
103+
if (any_pixels) {
104+
text_offset_x = (SIZE-1) - col;
105+
break;
106+
}
107+
}
108+
}
109+
110+
// Check if there's any overlap
111+
for (int row = 0; row < SIZE; ++row) {
112+
for (int col = text_offset_x; col < SIZE; ++col) {
113+
int opacity = qAlpha(icon.pixel(col, row));
114+
if (col >= text_offset_x) {
115+
opacity += qAlpha(scratch.pixel(col - text_offset_x, row));
116+
}
117+
if (opacity > 0xff) {
118+
// Overlap found, we're done
119+
return false;
120+
}
121+
}
122+
}
123+
return true;
124+
};
125+
int font_size = SIZE;
126+
while (font_size > 1) {
127+
font.setPixelSize(--font_size);
128+
scratch_painter.setFont(font);
129+
if (CheckSize(icon_in , label_in , /* align_right= */ false) &&
130+
CheckSize(icon_out, label_out, /* align_right= */ true)) break;
131+
}
132+
icon_in_painter .drawText(0, 0, SIZE, SIZE, Qt::AlignLeft | Qt::AlignBottom, label_in);
133+
icon_out_painter.drawText(0, 0, SIZE, SIZE, Qt::AlignRight | Qt::AlignBottom, label_out);
134+
}
135+
m_icon_conn_in = m_platform_style.TextColorIcon(QIcon(QPixmap::fromImage(icon_in)));
136+
m_icon_conn_out = m_platform_style.TextColorIcon(QIcon(QPixmap::fromImage(icon_out)));
137+
}
138+
38139
void PeerTableModel::startAutoRefresh()
39140
{
40141
timer->start();
@@ -73,9 +174,10 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const
73174
switch (column) {
74175
case NetNodeId:
75176
return (qint64)rec->nodeStats.nodeid;
177+
case Direction:
178+
return {};
76179
case Address:
77-
// prepend to peer address down-arrow symbol for inbound connection and up-arrow for outbound connection
78-
return QString::fromStdString((rec->nodeStats.fInbound ? "" : "") + rec->nodeStats.addrName);
180+
return QString::fromStdString(rec->nodeStats.addrName);
79181
case ConnectionType:
80182
return GUIUtil::ConnectionTypeToQString(rec->nodeStats.m_conn_type, /* prepend_direction */ false);
81183
case Network:
@@ -94,6 +196,7 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const
94196
switch (column) {
95197
case NetNodeId:
96198
return QVariant(Qt::AlignRight | Qt::AlignVCenter);
199+
case Direction:
97200
case Address:
98201
return {};
99202
case ConnectionType:
@@ -109,6 +212,8 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const
109212
assert(false);
110213
} else if (role == StatsRole) {
111214
return QVariant::fromValue(rec);
215+
} else if (index.column() == Direction && role == Qt::DecorationRole) {
216+
return rec->nodeStats.fInbound ? m_icon_conn_in : m_icon_conn_out;
112217
}
113218

114219
return QVariant();

src/qt/peertablemodel.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <net.h>
1010

1111
#include <QAbstractTableModel>
12+
#include <QIcon>
1213
#include <QList>
1314
#include <QModelIndex>
1415
#include <QStringList>
@@ -46,8 +47,10 @@ class PeerTableModel : public QAbstractTableModel
4647
void startAutoRefresh();
4748
void stopAutoRefresh();
4849

50+
// See also RPCConsole::ColumnWidths in rpcconsole.h
4951
enum ColumnIndex {
5052
NetNodeId = 0,
53+
Direction,
5154
Address,
5255
ConnectionType,
5356
Network,
@@ -82,10 +85,13 @@ public Q_SLOTS:
8285
QList<CNodeCombinedStats> m_peers_data{};
8386
interfaces::Node& m_node;
8487
const PlatformStyle& m_platform_style;
88+
void DrawIcons();
89+
QIcon m_icon_conn_in, m_icon_conn_out;
8590
const QStringList columns{
8691
/*: Title of Peers Table column which contains a
8792
unique number used to identify a connection. */
8893
tr("Peer"),
94+
"", // Direction column has no title
8995
/*: Title of Peers Table column which contains the
9096
IP/Onion/I2P address of the connected peer. */
9197
tr("Address"),

src/qt/peertablesortproxy.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ bool PeerTableSortProxy::lessThan(const QModelIndex& left_index, const QModelInd
2424
switch (static_cast<PeerTableModel::ColumnIndex>(left_index.column())) {
2525
case PeerTableModel::NetNodeId:
2626
return left_stats.nodeid < right_stats.nodeid;
27+
case PeerTableModel::Direction:
28+
return left_stats.fInbound > right_stats.fInbound; // default sort Inbound, then Outbound
2729
case PeerTableModel::Address:
2830
return left_stats.addrName.compare(right_stats.addrName) < 0;
2931
case PeerTableModel::ConnectionType:

src/qt/rpcconsole.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,7 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_
666666
ui->peerWidget->setContextMenuPolicy(Qt::CustomContextMenu);
667667

668668
if (!ui->peerWidget->horizontalHeader()->restoreState(m_peer_widget_header_state)) {
669+
ui->peerWidget->setColumnWidth(PeerTableModel::Direction, DIRECTION_COLUMN_WIDTH);
669670
ui->peerWidget->setColumnWidth(PeerTableModel::Address, ADDRESS_COLUMN_WIDTH);
670671
ui->peerWidget->setColumnWidth(PeerTableModel::Subversion, SUBVERSION_COLUMN_WIDTH);
671672
ui->peerWidget->setColumnWidth(PeerTableModel::Ping, PING_COLUMN_WIDTH);

src/qt/rpcconsole.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ public Q_SLOTS:
144144

145145
enum ColumnWidths
146146
{
147+
DIRECTION_COLUMN_WIDTH = 32,
147148
ADDRESS_COLUMN_WIDTH = 200,
148149
SUBVERSION_COLUMN_WIDTH = 150,
149150
PING_COLUMN_WIDTH = 80,

0 commit comments

Comments
 (0)