Skip to content

Commit c592f7c

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

File tree

4 files changed

+113
-10
lines changed

4 files changed

+113
-10
lines changed

src/qt/peertablemodel.cpp

Lines changed: 105 additions & 6 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

@@ -25,12 +31,107 @@ PeerTableModel::PeerTableModel(interfaces::Node& node, const PlatformStyle& plat
2531
connect(timer, &QTimer::timeout, this, &PeerTableModel::refresh);
2632
timer->setInterval(MODEL_UPDATE_DELAY);
2733

34+
DrawIcons();
35+
2836
// load initial data
2937
refresh();
3038
}
3139

3240
PeerTableModel::~PeerTableModel() = default;
3341

42+
void PeerTableModel::DrawIcons()
43+
{
44+
static constexpr auto SIZE = 32;
45+
static constexpr auto ARROW_HEIGHT = SIZE * 2 / 3;
46+
QImage icon_in(SIZE, SIZE, QImage::Format_Alpha8);
47+
icon_in.fill(Qt::transparent);
48+
QImage icon_out(icon_in);
49+
QPainter icon_in_painter(&icon_in);
50+
QPainter icon_out_painter(&icon_out);
51+
52+
// Arrow
53+
auto DrawArrow = [](const int x, QPainter& icon_painter) {
54+
icon_painter.setBrush(Qt::SolidPattern);
55+
QPoint shape[] = {
56+
{x, ARROW_HEIGHT / 2},
57+
{(SIZE-1) - x, 0},
58+
{(SIZE-1) - x, ARROW_HEIGHT-1},
59+
};
60+
icon_painter.drawConvexPolygon(shape, 3);
61+
};
62+
DrawArrow(0, icon_in_painter);
63+
DrawArrow(SIZE-1, icon_out_painter);
64+
65+
{
66+
//: Label on inbound connection icon
67+
const QString label_in = tr("IN");
68+
//: Label on outbound connection icon
69+
const QString label_out = tr("OUT");
70+
QImage scratch(SIZE, SIZE, QImage::Format_Alpha8);
71+
QPainter scratch_painter(&scratch);
72+
QFont font; // NOTE: Application default font
73+
font.setBold(true);
74+
auto CheckSize = [&](const QImage& icon, const QString& text, const bool align_right) {
75+
// Make sure it's at least able to fit (width only)
76+
if (scratch_painter.boundingRect(0, 0, SIZE, SIZE, 0, text).width() > SIZE) {
77+
return false;
78+
}
79+
80+
// Draw text on the scratch image
81+
// NOTE: QImage::fill doesn't like QPainter being active
82+
scratch_painter.setCompositionMode(QPainter::CompositionMode_Source);
83+
scratch_painter.fillRect(0, 0, SIZE, SIZE, Qt::transparent);
84+
scratch_painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
85+
scratch_painter.drawText(0, SIZE, text);
86+
87+
int text_offset_x = 0;
88+
if (align_right) {
89+
// Figure out how far right we can shift it
90+
for (int col = SIZE-1; col >= 0; --col) {
91+
bool any_pixels = false;
92+
for (int row = SIZE-1; row >= 0; --row) {
93+
int opacity = qAlpha(scratch.pixel(col, row));
94+
if (opacity > 0) {
95+
any_pixels = true;
96+
break;
97+
}
98+
}
99+
if (any_pixels) {
100+
text_offset_x = (SIZE-1) - col;
101+
break;
102+
}
103+
}
104+
}
105+
106+
// Check if there's any overlap
107+
for (int row = 0; row < SIZE; ++row) {
108+
for (int col = text_offset_x; col < SIZE; ++col) {
109+
int opacity = qAlpha(icon.pixel(col, row));
110+
if (col >= text_offset_x) {
111+
opacity += qAlpha(scratch.pixel(col - text_offset_x, row));
112+
}
113+
if (opacity > 0xff) {
114+
// Overlap found, we're done
115+
return false;
116+
}
117+
}
118+
}
119+
return true;
120+
};
121+
int font_size = SIZE;
122+
while (font_size > 1) {
123+
font.setPixelSize(--font_size);
124+
scratch_painter.setFont(font);
125+
if (CheckSize(icon_in , label_in , /* align_right= */ false) &&
126+
CheckSize(icon_out, label_out, /* align_right= */ true)) break;
127+
}
128+
icon_in_painter .drawText(0, 0, SIZE, SIZE, Qt::AlignLeft | Qt::AlignBottom, label_in);
129+
icon_out_painter.drawText(0, 0, SIZE, SIZE, Qt::AlignRight | Qt::AlignBottom, label_out);
130+
}
131+
m_icon_conn_in = m_platform_style.TextColorIcon(QIcon(QPixmap::fromImage(icon_in)));
132+
m_icon_conn_out = m_platform_style.TextColorIcon(QIcon(QPixmap::fromImage(icon_out)));
133+
}
134+
34135
void PeerTableModel::startAutoRefresh()
35136
{
36137
timer->start();
@@ -74,11 +175,7 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const
74175
case Address:
75176
return QString::fromStdString(rec->nodeStats.m_addr_name);
76177
case Direction:
77-
return QString(rec->nodeStats.fInbound ?
78-
//: An Inbound Connection from a Peer.
79-
tr("Inbound") :
80-
//: An Outbound Connection to a Peer.
81-
tr("Outbound"));
178+
return {};
82179
case ConnectionType:
83180
return GUIUtil::ConnectionTypeToQString(rec->nodeStats.m_conn_type, /*prepend_direction=*/false);
84181
case Network:
@@ -97,10 +194,10 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const
97194
switch (column) {
98195
case NetNodeId:
99196
case Age:
197+
case Direction:
100198
return QVariant(Qt::AlignRight | Qt::AlignVCenter);
101199
case Address:
102200
return {};
103-
case Direction:
104201
case ConnectionType:
105202
case Network:
106203
return QVariant(Qt::AlignCenter);
@@ -114,6 +211,8 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const
114211
assert(false);
115212
} else if (role == StatsRole) {
116213
return QVariant::fromValue(rec);
214+
} else if (index.column() == Direction && role == Qt::DecorationRole) {
215+
return rec->nodeStats.fInbound ? m_icon_conn_in : m_icon_conn_out;
117216
}
118217

119218
return QVariant();

src/qt/peertablemodel.h

Lines changed: 6 additions & 4 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,11 +47,12 @@ 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,
5153
Age,
52-
Address,
5354
Direction,
55+
Address,
5456
ConnectionType,
5557
Network,
5658
Ping,
@@ -81,19 +83,19 @@ public Q_SLOTS:
8183
QList<CNodeCombinedStats> m_peers_data{};
8284
interfaces::Node& m_node;
8385
const PlatformStyle& m_platform_style;
86+
void DrawIcons();
87+
QIcon m_icon_conn_in, m_icon_conn_out;
8488
const QStringList columns{
8589
/*: Title of Peers Table column which contains a
8690
unique number used to identify a connection. */
8791
tr("Peer"),
8892
/*: Title of Peers Table column which indicates the duration (length of time)
8993
since the peer connection started. */
9094
tr("Age"),
95+
"", // Direction column has no title
9196
/*: Title of Peers Table column which contains the
9297
IP/Onion/I2P address of the connected peer. */
9398
tr("Address"),
94-
/*: Title of Peers Table column which indicates the direction
95-
the peer connection was initiated from. */
96-
tr("Direction"),
9799
/*: Title of Peers Table column which describes the type of
98100
peer connection. The "type" describes why the connection exists. */
99101
tr("Type"),

src/qt/rpcconsole.cpp

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

682682
if (!ui->peerWidget->horizontalHeader()->restoreState(m_peer_widget_header_state)) {
683+
ui->peerWidget->setColumnWidth(PeerTableModel::Direction, DIRECTION_COLUMN_WIDTH);
683684
ui->peerWidget->setColumnWidth(PeerTableModel::Address, ADDRESS_COLUMN_WIDTH);
684685
ui->peerWidget->setColumnWidth(PeerTableModel::Subversion, SUBVERSION_COLUMN_WIDTH);
685686
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
@@ -148,6 +148,7 @@ public Q_SLOTS:
148148

149149
enum ColumnWidths
150150
{
151+
DIRECTION_COLUMN_WIDTH = 32,
151152
ADDRESS_COLUMN_WIDTH = 200,
152153
SUBVERSION_COLUMN_WIDTH = 150,
153154
PING_COLUMN_WIDTH = 80,

0 commit comments

Comments
 (0)