Skip to content

Commit ed4b94a

Browse files
committed
Merge branch 'qt_peers_directionarrow-25+knots' into gui_peers_bump_setting_keys-25+k
2 parents bbbf89a + 89b43dd commit ed4b94a

File tree

9 files changed

+156
-23
lines changed

9 files changed

+156
-23
lines changed

src/qt/bitcoin.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ void BitcoinApplication::initializeResult(bool success, interfaces::BlockAndHead
404404

405405
// Log this only after AppInitMain finishes, as then logging setup is guaranteed complete
406406
qInfo() << "Platform customization:" << platformStyle->getName();
407-
clientModel = new ClientModel(node(), optionsModel);
407+
clientModel = new ClientModel(node(), optionsModel, *platformStyle);
408408
window->setClientModel(clientModel, &tip_info);
409409
#ifdef ENABLE_WALLET
410410
if (WalletModel::isWalletEnabled()) {

src/qt/clientmodel.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
static int64_t nLastHeaderTipUpdateNotification = 0;
3131
static int64_t nLastBlockTipUpdateNotification = 0;
3232

33-
ClientModel::ClientModel(interfaces::Node& node, OptionsModel *_optionsModel, QObject *parent) :
33+
ClientModel::ClientModel(interfaces::Node& node, OptionsModel *_optionsModel, const PlatformStyle& platform_style, QObject *parent) :
3434
QObject(parent),
3535
m_node(node),
3636
optionsModel(_optionsModel),
@@ -39,7 +39,7 @@ ClientModel::ClientModel(interfaces::Node& node, OptionsModel *_optionsModel, QO
3939
cachedBestHeaderHeight = -1;
4040
cachedBestHeaderTime = -1;
4141

42-
peerTableModel = new PeerTableModel(m_node, this);
42+
peerTableModel = new PeerTableModel(m_node, platform_style, this);
4343
m_peer_table_sort_proxy = new PeerTableSortProxy(this);
4444
m_peer_table_sort_proxy->setSourceModel(peerTableModel);
4545

src/qt/clientmodel.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class CBlockIndex;
1818
class OptionsModel;
1919
class PeerTableModel;
2020
class PeerTableSortProxy;
21+
class PlatformStyle;
2122
enum class SynchronizationState;
2223

2324
namespace interfaces {
@@ -55,7 +56,7 @@ class ClientModel : public QObject
5556
Q_OBJECT
5657

5758
public:
58-
explicit ClientModel(interfaces::Node& node, OptionsModel *optionsModel, QObject *parent = nullptr);
59+
explicit ClientModel(interfaces::Node& node, OptionsModel *optionsModel, const PlatformStyle&, QObject *parent = nullptr);
5960
~ClientModel();
6061

6162
interfaces::Node& node() const { return m_node; }

src/qt/peertablemodel.cpp

Lines changed: 121 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,144 @@
66

77
#include <qt/guiconstants.h>
88
#include <qt/guiutil.h>
9+
#include <qt/platformstyle.h>
910

1011
#include <interfaces/node.h>
1112

1213
#include <utility>
1314

15+
#include <QBrush>
16+
#include <QFont>
17+
#include <QFontInfo>
18+
#include <QImage>
19+
#include <QPainter>
20+
#include <QPixmap>
1421
#include <QList>
1522
#include <QTimer>
1623

17-
PeerTableModel::PeerTableModel(interfaces::Node& node, QObject* parent)
24+
PeerTableModel::PeerTableModel(interfaces::Node& node, const PlatformStyle& platform_style, QObject* parent)
1825
: QAbstractTableModel(parent),
19-
m_node(node)
26+
m_node(node),
27+
m_platform_style(platform_style)
2028
{
2129
// set up timer for auto refresh
2230
timer = new QTimer(this);
2331
connect(timer, &QTimer::timeout, this, &PeerTableModel::refresh);
2432
timer->setInterval(MODEL_UPDATE_DELAY);
2533

34+
DrawIcons();
35+
2636
// load initial data
2737
refresh();
2838
}
2939

3040
PeerTableModel::~PeerTableModel() = default;
3141

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+
135+
void PeerTableModel::updatePalette()
136+
{
137+
m_icon_conn_in = m_platform_style.TextColorIcon(m_icon_conn_in);
138+
m_icon_conn_out = m_platform_style.TextColorIcon(m_icon_conn_out);
139+
if (m_peers_data.empty()) return;
140+
Q_EMIT dataChanged(
141+
createIndex(0, Direction),
142+
createIndex(m_peers_data.size() - 1, Direction),
143+
QVector<int>{Qt::DecorationRole}
144+
);
145+
}
146+
32147
void PeerTableModel::startAutoRefresh()
33148
{
34149
timer->start();
@@ -72,11 +187,7 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const
72187
case Address:
73188
return QString::fromStdString(rec->nodeStats.m_addr_name);
74189
case Direction:
75-
return QString(rec->nodeStats.fInbound ?
76-
//: An Inbound Connection from a Peer.
77-
tr("Inbound") :
78-
//: An Outbound Connection to a Peer.
79-
tr("Outbound"));
190+
return {};
80191
case ConnectionType:
81192
return GUIUtil::ConnectionTypeToQString(rec->nodeStats.m_conn_type, /*prepend_direction=*/false);
82193
case Network:
@@ -95,10 +206,10 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const
95206
switch (column) {
96207
case NetNodeId:
97208
case Age:
209+
case Direction:
98210
return QVariant(Qt::AlignRight | Qt::AlignVCenter);
99211
case Address:
100212
return {};
101-
case Direction:
102213
case ConnectionType:
103214
case Network:
104215
return QVariant(Qt::AlignCenter);
@@ -112,6 +223,8 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const
112223
assert(false);
113224
} else if (role == StatsRole) {
114225
return QVariant::fromValue(rec);
226+
} else if (index.column() == Direction && role == Qt::DecorationRole) {
227+
return rec->nodeStats.fInbound ? m_icon_conn_in : m_icon_conn_out;
115228
}
116229

117230
return QVariant();

src/qt/peertablemodel.h

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

1111
#include <QAbstractTableModel>
12+
#include <QIcon>
1213
#include <QList>
1314
#include <QModelIndex>
1415
#include <QStringList>
1516
#include <QVariant>
1617

1718
class PeerTablePriv;
19+
class PlatformStyle;
1820

1921
namespace interfaces {
2022
class Node;
@@ -40,16 +42,17 @@ class PeerTableModel : public QAbstractTableModel
4042
Q_OBJECT
4143

4244
public:
43-
explicit PeerTableModel(interfaces::Node& node, QObject* parent);
45+
explicit PeerTableModel(interfaces::Node& node, const PlatformStyle&, QObject* parent);
4446
~PeerTableModel();
4547
void startAutoRefresh();
4648
void stopAutoRefresh();
4749

50+
// See also RPCConsole::ColumnWidths in rpcconsole.h
4851
enum ColumnIndex {
4952
NetNodeId = 0,
5053
Age,
51-
Address,
5254
Direction,
55+
Address,
5356
ConnectionType,
5457
Network,
5558
Ping,
@@ -74,24 +77,26 @@ class PeerTableModel : public QAbstractTableModel
7477

7578
public Q_SLOTS:
7679
void refresh();
80+
void updatePalette();
7781

7882
private:
7983
//! Internal peer data structure.
8084
QList<CNodeCombinedStats> m_peers_data{};
8185
interfaces::Node& m_node;
86+
const PlatformStyle& m_platform_style;
87+
void DrawIcons();
88+
QIcon m_icon_conn_in, m_icon_conn_out;
8289
const QStringList columns{
8390
/*: Title of Peers Table column which contains a
8491
unique number used to identify a connection. */
85-
tr("Peer"),
92+
tr("id"),
8693
/*: Title of Peers Table column which indicates the duration (length of time)
8794
since the peer connection started. */
8895
tr("Age"),
96+
"", // Direction column has no title
8997
/*: Title of Peers Table column which contains the
9098
IP/Onion/I2P address of the connected peer. */
9199
tr("Address"),
92-
/*: Title of Peers Table column which indicates the direction
93-
the peer connection was initiated from. */
94-
tr("Direction"),
95100
/*: Title of Peers Table column which describes the type of
96101
peer connection. The "type" describes why the connection exists. */
97102
tr("Type"),
@@ -106,7 +111,7 @@ public Q_SLOTS:
106111
tr("Sent"),
107112
/*: Title of Peers Table column which indicates the total amount of
108113
network information we have received from the peer. */
109-
tr("Received"),
114+
tr("Recv'd"),
110115
/*: Title of Peers Table column which contains the peer's
111116
User Agent string. */
112117
tr("User Agent")};

src/qt/rpcconsole.cpp

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <QAbstractItemModel>
3131
#include <QDateTime>
3232
#include <QFont>
33+
#include <QFontMetrics>
3334
#include <QKeyEvent>
3435
#include <QKeySequence>
3536
#include <QLatin1String>
@@ -673,18 +674,27 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_
673674
connect(model, &ClientModel::mempoolSizeChanged, this, &RPCConsole::setMempoolSize);
674675

675676
// set up peer table
677+
clientModel->getPeerTableModel()->updatePalette();
676678
ui->peerWidget->setModel(model->peerTableSortProxy());
677679
ui->peerWidget->verticalHeader()->hide();
678680
ui->peerWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
679681
ui->peerWidget->setSelectionMode(QAbstractItemView::ExtendedSelection);
680682
ui->peerWidget->setContextMenuPolicy(Qt::CustomContextMenu);
681683

682684
if (!ui->peerWidget->horizontalHeader()->restoreState(m_peer_widget_header_state)) {
685+
const QFontMetrics fm = ui->peerWidget->fontMetrics();
686+
ui->peerWidget->setColumnWidth(PeerTableModel::NetNodeId, GUIUtil::TextWidth(fm, QStringLiteral("99999")));
687+
ui->peerWidget->setColumnWidth(PeerTableModel::Age, GUIUtil::TextWidth(fm, GUIUtil::FormatPeerAge(std::chrono::hours{23976 /* 999 days */})));
688+
ui->peerWidget->setColumnWidth(PeerTableModel::Direction, DIRECTION_COLUMN_WIDTH);
683689
ui->peerWidget->setColumnWidth(PeerTableModel::Address, ADDRESS_COLUMN_WIDTH);
690+
ui->peerWidget->setColumnWidth(PeerTableModel::ConnectionType, GUIUtil::TextWidth(fm, GUIUtil::ConnectionTypeToQString(ConnectionType::ADDR_FETCH /* TODO: Find the WIDEST string? */, /*prepend_direction=*/false)));
691+
const auto bytesize_width = GUIUtil::TextWidth(fm, GUIUtil::formatBytes(999'000'000'000) + QStringLiteral("x"));
692+
ui->peerWidget->setColumnWidth(PeerTableModel::Network, GUIUtil::TextWidth(fm, qvariant_cast<QString>(model->peerTableSortProxy()->headerData(PeerTableModel::ColumnIndex::Network, Qt::Horizontal, Qt::DisplayRole)) /* TODO: Find the WIDEST string? */ + QStringLiteral("x")));
684693
ui->peerWidget->setColumnWidth(PeerTableModel::Subversion, SUBVERSION_COLUMN_WIDTH);
685694
ui->peerWidget->setColumnWidth(PeerTableModel::Ping, PING_COLUMN_WIDTH);
695+
ui->peerWidget->setColumnWidth(PeerTableModel::Sent, bytesize_width);
696+
ui->peerWidget->setColumnWidth(PeerTableModel::Received, bytesize_width);
686697
}
687-
ui->peerWidget->horizontalHeader()->setSectionResizeMode(PeerTableModel::Age, QHeaderView::ResizeToContents);
688698
ui->peerWidget->horizontalHeader()->setStretchLastSection(true);
689699
ui->peerWidget->setItemDelegateForColumn(PeerTableModel::NetNodeId, new PeerIdViewDelegate(this));
690700

@@ -717,7 +727,6 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_
717727
ui->banlistWidget->setColumnWidth(BanTableModel::Address, BANSUBNET_COLUMN_WIDTH);
718728
ui->banlistWidget->setColumnWidth(BanTableModel::Bantime, BANTIME_COLUMN_WIDTH);
719729
}
720-
ui->banlistWidget->horizontalHeader()->setSectionResizeMode(BanTableModel::Address, QHeaderView::ResizeToContents);
721730
ui->banlistWidget->horizontalHeader()->setStretchLastSection(true);
722731

723732
// create ban table context menu
@@ -925,6 +934,10 @@ void RPCConsole::changeEvent(QEvent* e)
925934
QUrl(ICON_MAPPING[i].url),
926935
platformStyle->SingleColorImage(ICON_MAPPING[i].source).scaled(QSize(consoleFontSize * 2, consoleFontSize * 2), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
927936
}
937+
938+
if (clientModel && clientModel->getPeerTableModel()) {
939+
clientModel->getPeerTableModel()->updatePalette();
940+
}
928941
}
929942

930943
QWidget::changeEvent(e);

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,

src/qt/test/addressbooktests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ void TestAddAddressesToSendBook(interfaces::Node& node)
130130
OptionsModel optionsModel(node);
131131
bilingual_str error;
132132
QVERIFY(optionsModel.Init(error));
133-
ClientModel clientModel(node, &optionsModel);
133+
ClientModel clientModel(node, &optionsModel, *platformStyle);
134134
WalletContext& context = *node.walletLoader().context();
135135
AddWallet(context, wallet);
136136
WalletModel walletModel(interfaces::MakeWallet(context, wallet), clientModel, platformStyle.get());

src/qt/test/wallettests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ struct MiniGUI {
239239
MiniGUI(interfaces::Node& node, const PlatformStyle* platformStyle) : sendCoinsDialog(platformStyle), transactionView(platformStyle), optionsModel(node) {
240240
bilingual_str error;
241241
QVERIFY(optionsModel.Init(error));
242-
clientModel = std::make_unique<ClientModel>(node, &optionsModel);
242+
clientModel = std::make_unique<ClientModel>(node, &optionsModel, *platformStyle);
243243
}
244244

245245
void initModelForWallet(interfaces::Node& node, const std::shared_ptr<CWallet>& wallet, const PlatformStyle* platformStyle)

0 commit comments

Comments
 (0)