12
12
13
13
#include < utility>
14
14
15
+ #include < QBrush>
16
+ #include < QFont>
17
+ #include < QFontInfo>
18
+ #include < QImage>
19
+ #include < QPainter>
20
+ #include < QPixmap>
15
21
#include < QList>
16
22
#include < QTimer>
17
23
@@ -26,6 +32,8 @@ PeerTableModel::PeerTableModel(interfaces::Node& node, const PlatformStyle& plat
26
32
connect (timer, &QTimer::timeout, this , &PeerTableModel::refresh);
27
33
timer->setInterval (MODEL_UPDATE_DELAY);
28
34
35
+ DrawIcons ();
36
+
29
37
// load initial data
30
38
refresh ();
31
39
}
@@ -35,6 +43,99 @@ PeerTableModel::~PeerTableModel()
35
43
// Intentionally left empty
36
44
}
37
45
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
+
38
139
void PeerTableModel::startAutoRefresh ()
39
140
{
40
141
timer->start ();
@@ -73,9 +174,10 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const
73
174
switch (column) {
74
175
case NetNodeId:
75
176
return (qint64)rec->nodeStats .nodeid ;
177
+ case Direction:
178
+ return {};
76
179
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 );
79
181
case ConnectionType:
80
182
return GUIUtil::ConnectionTypeToQString (rec->nodeStats .m_conn_type , /* prepend_direction */ false );
81
183
case Network:
@@ -94,6 +196,7 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const
94
196
switch (column) {
95
197
case NetNodeId:
96
198
return QVariant (Qt::AlignRight | Qt::AlignVCenter);
199
+ case Direction:
97
200
case Address:
98
201
return {};
99
202
case ConnectionType:
@@ -109,6 +212,8 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const
109
212
assert (false );
110
213
} else if (role == StatsRole) {
111
214
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;
112
217
}
113
218
114
219
return QVariant ();
0 commit comments