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
@@ -25,12 +31,107 @@ PeerTableModel::PeerTableModel(interfaces::Node& node, const PlatformStyle& plat
25
31
connect (timer, &QTimer::timeout, this , &PeerTableModel::refresh);
26
32
timer->setInterval (MODEL_UPDATE_DELAY);
27
33
34
+ DrawIcons ();
35
+
28
36
// load initial data
29
37
refresh ();
30
38
}
31
39
32
40
PeerTableModel::~PeerTableModel () = default ;
33
41
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
+
34
135
void PeerTableModel::startAutoRefresh ()
35
136
{
36
137
timer->start ();
@@ -74,11 +175,7 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const
74
175
case Address:
75
176
return QString::fromStdString (rec->nodeStats .m_addr_name );
76
177
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 {};
82
179
case ConnectionType:
83
180
return GUIUtil::ConnectionTypeToQString (rec->nodeStats .m_conn_type , /* prepend_direction=*/ false );
84
181
case Network:
@@ -97,10 +194,10 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const
97
194
switch (column) {
98
195
case NetNodeId:
99
196
case Age:
197
+ case Direction:
100
198
return QVariant (Qt::AlignRight | Qt::AlignVCenter);
101
199
case Address:
102
200
return {};
103
- case Direction:
104
201
case ConnectionType:
105
202
case Network:
106
203
return QVariant (Qt::AlignCenter);
@@ -114,6 +211,8 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const
114
211
assert (false );
115
212
} else if (role == StatsRole) {
116
213
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;
117
216
}
118
217
119
218
return QVariant ();
0 commit comments