diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 35d5b0004a0..4931c3bce5c 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -22,6 +22,7 @@ QT_FORMS_UI = \ qt/forms/helpmessagedialog.ui \ qt/forms/intro.ui \ qt/forms/modaloverlay.ui \ + qt/forms/mempoolstats.ui \ qt/forms/openuridialog.ui \ qt/forms/optionsdialog.ui \ qt/forms/overviewpage.ui \ @@ -55,6 +56,7 @@ QT_MOC_CPP = \ qt/moc_intro.cpp \ qt/moc_macdockiconhandler.cpp \ qt/moc_macnotificationhandler.cpp \ + qt/moc_mempoolstats.cpp \ qt/moc_modaloverlay.cpp \ qt/moc_notificator.cpp \ qt/moc_openuridialog.cpp \ @@ -128,6 +130,7 @@ BITCOIN_QT_H = \ qt/macdockiconhandler.h \ qt/macnotificationhandler.h \ qt/macos_appnap.h \ + qt/mempoolstats.h \ qt/modaloverlay.h \ qt/networkstyle.h \ qt/notificator.h \ @@ -231,6 +234,7 @@ BITCOIN_QT_BASE_CPP = \ qt/guiutil.cpp \ qt/initexecutor.cpp \ qt/intro.cpp \ + qt/mempoolstats.cpp \ qt/modaloverlay.cpp \ qt/networkstyle.cpp \ qt/notificator.cpp \ diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 974156e6e1a..249a2620432 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -50,6 +50,7 @@ struct BlockAndHeaderTipInfo double verification_progress; }; + //! External signer interface used by the GUI. class ExternalSigner { @@ -60,6 +61,24 @@ class ExternalSigner virtual std::string getName() = 0; }; +class mempool_feeinfo { +public: + uint64_t total_size; + uint64_t total_fee; + uint64_t tx_count; + CAmount fee_from; + CAmount fee_to; + + //TODO: remove + // added for storing and loading a mempool set during development to avoid waiting hours for collecting enought samples + SERIALIZE_METHODS(mempool_feeinfo, obj) + { + READWRITE(obj.total_size, obj.total_fee, obj.tx_count, obj.fee_from, obj.fee_to); + } +}; + +typedef std::vector mempool_feehistogram; + //! Top-level interface for a bitcoin node (bitcoind process). class Node { @@ -136,6 +155,9 @@ class Node //! Get mempool dynamic usage. virtual size_t getMempoolDynamicUsage() = 0; + //! Get mempool fee histogram + virtual mempool_feehistogram getMempoolFeeHistogram() = 0; + //! Get header tip height and time. virtual bool getHeaderTip(int& height, int64_t& block_time) = 0; diff --git a/src/net.cpp b/src/net.cpp index d1f1b540072..f6be7aa5a95 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2532,7 +2532,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) if (fListen && !InitBinds(connOptions)) { if (m_client_interface) { m_client_interface->ThreadSafeMessageBox( - _("Failed to listen on any port. Use -listen=0 if you want this."), + _("Failed to listen on any port.\nUse -listen=0 if you want this."), "", CClientUIInterface::MSG_ERROR); } return false; diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 0f24211a0ec..cf51455e770 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -209,6 +209,58 @@ class NodeImpl : public Node int64_t getTotalBytesSent() override { return m_context->connman ? m_context->connman->GetTotalBytesSent() : 0; } size_t getMempoolSize() override { return m_context->mempool ? m_context->mempool->size() : 0; } size_t getMempoolDynamicUsage() override { return m_context->mempool ? m_context->mempool->DynamicMemoryUsage() : 0; } + interfaces::mempool_feehistogram getMempoolFeeHistogram() override { + /* TODO: define log scale formular for dynamically creating the + * feelimits but with the property of not constantly changing + * (and thus screw up client implementations) */ + static const std::vector feelimits{1, 2, 3, 4, 5, 6, 7, 8, 10, + 12, 14, 17, 20, 25, 30, 40, 50, 60, 70, 80, 100, + 120, 140, 170, 200, 250, 300, 400, 500, 600, 700, 800, 1000, + 1200, 1400, 1700, 2000, 2500, 3000, 4000, 5000, 6000, 7000, 8000, 10000}; + + /* keep histogram per... + * ... cumulated tx sizes + * ... txns (count) + * ... cumulated fees */ + std::vector sizes(feelimits.size(), 0); + std::vector count(feelimits.size(), 0); + std::vector fees(feelimits.size(), 0); + + { + LOCK(m_context->mempool->cs); + for (const CTxMemPoolEntry& e : m_context->mempool->mapTx) { + int size = (int)e.GetTxSize(); + CAmount fee = e.GetFee(); + uint64_t asize = e.GetSizeWithAncestors(); + CAmount afees = e.GetModFeesWithAncestors(); + uint64_t dsize = e.GetSizeWithDescendants(); + CAmount dfees = e.GetModFeesWithDescendants(); + + CAmount fpb = fee / size; //fee per byte + CAmount afpb = afees / asize; //fee per byte including ancestors + CAmount dfpb = dfees / dsize; //fee per byte including descendants + CAmount tfpb = (afees + dfees - fee) / (asize + dsize - size); + CAmount feeperbyte = std::max(std::min(dfpb, tfpb), std::min(fpb, afpb)); + + // distribute feerates into feelimits + for (size_t i = 0; i < feelimits.size(); i++) { + if (feeperbyte >= feelimits[i] && (i == feelimits.size() - 1 || feeperbyte < feelimits[i + 1])) { + sizes[i] += size; + count[i]++; + fees[i] += fee; + break; + } + } + } + } + interfaces::mempool_feehistogram feeinfo; + for (size_t i = 0; i < feelimits.size(); i++) { + feeinfo.push_back({sizes[i], fees[i], count[i], feelimits[i], (i == feelimits.size() - 1 ? std::numeric_limits::max() : feelimits[i + 1])}); + } + + return feeinfo; + } + bool getHeaderTip(int& height, int64_t& block_time) override { LOCK(::cs_main); diff --git a/src/qt/bitcoin-qt.cflags b/src/qt/bitcoin-qt.cflags new file mode 100644 index 00000000000..68d51653007 --- /dev/null +++ b/src/qt/bitcoin-qt.cflags @@ -0,0 +1 @@ +-std=c17 \ No newline at end of file diff --git a/src/qt/bitcoin-qt.cxxflags b/src/qt/bitcoin-qt.cxxflags new file mode 100644 index 00000000000..6435dfce2fb --- /dev/null +++ b/src/qt/bitcoin-qt.cxxflags @@ -0,0 +1 @@ +-std=c++17 \ No newline at end of file diff --git a/src/qt/bitcoin.qrc b/src/qt/bitcoin.qrc index fed373e551c..7ec6190ee6e 100644 --- a/src/qt/bitcoin.qrc +++ b/src/qt/bitcoin.qrc @@ -84,6 +84,7 @@ res/animation/spinner-035.png - res/fonts/RobotoMono-Bold.ttf + res/fonts/RobotoMono-Regular.ttf + res/fonts/RobotoMono-Bold.ttf diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 81a1d88d203..dcd86c6919b 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -965,6 +965,13 @@ void BitcoinGUI::setNetworkActive(bool network_active) rpcConsole->setTabFocus(RPCConsole::TabTypes::PEERS); showDebugWindow(); }); + m_network_context_menu->addAction( + //: A context menu item. The "Mempool tab" is an element of the "Node window". + tr("Show Mempool tab"), + [this] { + rpcConsole->setTabFocus(RPCConsole::TabTypes::MEMPOOL); + showDebugWindow(); + }); m_network_context_menu->addAction( network_active ? //: A context menu item. diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index c86cb16af66..c443279ff85 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +24,7 @@ #include #include +#include #include #include @@ -51,6 +53,19 @@ ClientModel::ClientModel(interfaces::Node& node, OptionsModel *_optionsModel, QO connect(timer, &QTimer::timeout, [this] { // no locking required at this point // the following calls will acquire the required lock + + int64_t now = GetTime(); + if (m_mempool_feehist_last_sample_timestamp == 0 || static_cast(m_mempool_feehist_last_sample_timestamp)+static_cast(m_mempool_collect_intervall) < static_cast(now)) { + QMutexLocker locker(&m_mempool_locker); + interfaces::mempool_feehistogram fee_histogram = m_node.getMempoolFeeHistogram(); + m_mempool_feehist.push_back({now, fee_histogram}); + if (m_mempool_feehist.size() > m_mempool_max_samples) { + m_mempool_feehist.erase(m_mempool_feehist.begin(), m_mempool_feehist.begin()+1); + } + m_mempool_feehist_last_sample_timestamp = now; + Q_EMIT mempoolFeeHistChanged(); + } + Q_EMIT mempoolSizeChanged(m_node.getMempoolSize(), m_node.getMempoolDynamicUsage()); Q_EMIT bytesChanged(m_node.getTotalBytesRecv(), m_node.getTotalBytesSent()); }); diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index 7a199ef19cb..02a50558a94 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_QT_CLIENTMODEL_H #define BITCOIN_QT_CLIENTMODEL_H +#include #include #include @@ -13,6 +14,8 @@ #include #include +#include + class BanTableModel; class CBlockIndex; class OptionsModel; @@ -20,11 +23,6 @@ class PeerTableModel; class PeerTableSortProxy; enum class SynchronizationState; -namespace interfaces { -class Handler; -class Node; -} - QT_BEGIN_NAMESPACE class QTimer; QT_END_NAMESPACE @@ -87,6 +85,13 @@ class ClientModel : public QObject Mutex m_cached_tip_mutex; uint256 m_cached_tip_blocks GUARDED_BY(m_cached_tip_mutex){}; + typedef std::pair> mempool_feehist_sample; //!< sample plus timestamp + mutable QMutex m_mempool_locker; + const static size_t m_mempool_max_samples{540}; + const static size_t m_mempool_collect_intervall{20}; // 540*20 = 3h of sample window + std::vector m_mempool_feehist; + std::atomic m_mempool_feehist_last_sample_timestamp{0}; + private: interfaces::Node& m_node; std::unique_ptr m_handler_show_progress; @@ -111,6 +116,7 @@ class ClientModel : public QObject void numConnectionsChanged(int count); void numBlocksChanged(int count, const QDateTime& blockDate, double nVerificationProgress, bool header, SynchronizationState sync_state); void mempoolSizeChanged(long count, size_t mempoolSizeInBytes); + void mempoolFeeHistChanged(); void networkActiveChanged(bool networkActive); void alertsChanged(const QString &warnings); void bytesChanged(quint64 totalBytesIn, quint64 totalBytesOut); diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui index 15e0d3fad99..fc5bd213647 100644 --- a/src/qt/forms/debugwindow.ui +++ b/src/qt/forms/debugwindow.ui @@ -6,8 +6,8 @@ 0 0 - 740 - 430 + 777 + 451 @@ -36,7 +36,7 @@ - 0 + 3 @@ -478,7 +478,7 @@ - + :/icons/fontsmaller:/icons/fontsmaller @@ -498,7 +498,7 @@ - + :/icons/fontbigger:/icons/fontbigger @@ -521,7 +521,7 @@ - + :/icons/remove:/icons/remove @@ -576,7 +576,7 @@ - + :/icons/prompticon :/icons/prompticon:/icons/prompticon @@ -612,79 +612,32 @@ &Network Traffic - + - - - - - - 0 - 0 - - - - - - - - - - 1 - - - 288 - - - 12 - - - 6 - - - Qt::Horizontal - - - - - - - - 100 - 0 - - - - Qt::AlignCenter - - - - - - - &Reset - - - false - - - - - - + + + + 0 + 0 + + + - + - Totals + + + + true - + - + - + 0 @@ -740,7 +693,7 @@ - + Received @@ -762,9 +715,9 @@ - + - + 0 @@ -820,7 +773,7 @@ - + Sent @@ -841,26 +794,89 @@ - - - - Qt::Vertical - - - - 20 - 407 - - - - + + + + + 105 + 0 + + + + 1 + + + 288 + + + 12 + + + 6 + + + Qt::Horizontal + + + + + + + + 60 + 0 + + + + Qt::AlignCenter + + + + + + + &Reset + + + false + + + + + + + 747 + 0 + + + + Mempool + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + &Peers @@ -1030,8 +1046,8 @@ 0 0 - 300 - 426 + 274 + 616 @@ -1628,9 +1644,13 @@ clear() + + MempoolStats + QWidget +
qt/mempoolstats.h
+ 1 +
- - - + diff --git a/src/qt/forms/debugwindow.ui.autosave b/src/qt/forms/debugwindow.ui.autosave new file mode 100644 index 00000000000..00a2d9045fb --- /dev/null +++ b/src/qt/forms/debugwindow.ui.autosave @@ -0,0 +1,1664 @@ + + + RPCConsole + + + + 0 + 0 + 777 + 451 + + + + Node window + + + + + + false + + + QLabel { background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop:0 #F0D0A0, stop:1 #F8D488); color:#000000; } + + + true + + + 3 + + + Qt::TextSelectableByMouse + + + + + + + 3 + + + + &Information + + + + 12 + + + + + + 75 + true + + + + General + + + + + + + Client version + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + User Agent + + + 10 + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Datadir + + + + + + + IBeamCursor + + + To specify a non-default location of the data directory use the '%1' option. + + + N/A + + + Qt::PlainText + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Blocksdir + + + + + + + IBeamCursor + + + To specify a non-default location of the blocks directory use the '%1' option. + + + N/A + + + Qt::PlainText + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Startup time + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 75 + true + + + + Network + + + + + + + Name + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Number of connections + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 75 + true + + + + Block chain + + + + + + + Current block height + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Last block time + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 75 + true + + + + Memory Pool + + + + + + + Current number of transactions + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Memory usage + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + 3 + + + + + Qt::Vertical + + + + 10 + 5 + + + + + + + + Debug log file + + + + + + + Open the %1 debug log file from the current data directory. This can take a few seconds for large log files. + + + &Open + + + false + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + &Console + + + + 3 + + + 5 + + + + + 4 + + + + + Wallet: + + + + + + + QComboBox::AdjustToContents + + + + (none) + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 24 + 24 + + + + Decrease font size + + + + + + + :/icons/fontsmaller:/icons/fontsmaller + + + + 24 + 16 + + + + false + + + true + + + + + + + + 24 + 24 + + + + Increase font size + + + + + + + :/icons/fontbigger:/icons/fontbigger + + + + 24 + 16 + + + + false + + + true + + + + + + + + 24 + 24 + + + + Clear console + + + Qt::LeftToRight + + + + + + + :/icons/remove:/icons/remove + + + Ctrl+L + + + false + + + true + + + + + + + + + + 0 + 100 + + + + true + + + false + + + 2 + + + + + + + 3 + + + + + false + + + + 16 + 24 + + + + + + + + :/icons/prompticon + :/icons/prompticon:/icons/prompticon + + + + 14 + 14 + + + + false + + + true + + + + + + + false + + + + + + + + + + + + + &Network Traffic + + + + + + + + + 0 + 0 + + + + + + + + + + 1 + + + 288 + + + 12 + + + 6 + + + Qt::Horizontal + + + + + + + + 100 + 0 + + + + Qt::AlignCenter + + + + + + + &Reset + + + false + + + + + + + + + + + + + Totals + + + + + + + + + 0 + 0 + + + + + 10 + 0 + + + + + + + + + 0 + 255 + 0 + + + + + + + + + 0 + 255 + 0 + + + + + + + + + 0 + 255 + 0 + + + + + + + + Qt::Horizontal + + + + + + + Received + + + + + + + + 50 + 0 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + 0 + 0 + + + + + 10 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + Qt::Horizontal + + + + + + + Sent + + + + + + + + 50 + 0 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Vertical + + + + 20 + 407 + + + + + + + + + + + + + + &Peers + + + + + + Qt::Horizontal + + + false + + + + + 1 + 0 + + + + + 400 + 0 + + + + + + + false + + + true + + + Qt::ElideMiddle + + + true + + + false + + + false + + + + + + + + 0 + 0 + + + + + 0 + 32 + + + + + 16777215 + 32 + + + + + 12 + + + + IBeamCursor + + + Banned peers + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + true + + + Qt::NoTextInteraction + + + + + + + false + + + true + + + true + + + false + + + + + + + + + 0 + 0 + + + + + 300 + 0 + + + + + + + + 0 + 0 + + + + + 0 + 32 + + + + + 10 + + + + IBeamCursor + + + Select a peer to view detailed information. + + + Qt::AlignHCenter|Qt::AlignTop + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + true + + + + + 0 + 0 + 274 + 616 + + + + + + + Permissions + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + The direction and type of peer connection: %1 + + + Direction/Type + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + The network protocol this peer is connected through: IPv4, IPv6, Onion, I2P, or CJDNS. + + + Network + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Version + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + User Agent + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Services + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Whether the peer requested us to relay transactions. + + + Wants Tx Relay + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + High bandwidth BIP152 compact block relay: %1 + + + High Bandwidth + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Starting Block + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Synced Headers + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Synced Blocks + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Connection Time + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Elapsed time since a novel block passing initial validity checks was received from this peer. + + + Last Block + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Elapsed time since a novel transaction accepted into our mempool was received from this peer. + + + Last Tx + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Last Send + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Last Receive + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Sent + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Received + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Ping Time + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + The duration of a currently outstanding ping. + + + Ping Wait + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Min Ping + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Time Offset + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + The mapped Autonomous System used for diversifying peer selection. + + + Mapped AS + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + + + + + TrafficGraphWidget + QWidget +
qt/trafficgraphwidget.h
+ 1 + + clear() + +
+
+ + +
diff --git a/src/qt/forms/mempoolstats.ui b/src/qt/forms/mempoolstats.ui new file mode 100644 index 00000000000..a6df26c2637 --- /dev/null +++ b/src/qt/forms/mempoolstats.ui @@ -0,0 +1,46 @@ + + + MempoolStats + + + + 0 + 0 + 800 + 640 + + + + + 800 + 640 + + + + Mempool Stats + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::ScrollBarAlwaysOff + + + + + + + + diff --git a/src/qt/mempoolconstants.h b/src/qt/mempoolconstants.h new file mode 100644 index 00000000000..79008f205d7 --- /dev/null +++ b/src/qt/mempoolconstants.h @@ -0,0 +1,50 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +static const char *LABEL_FONT = "Roboto Mono"; +static int LABEL_TITLE_SIZE = 22; +static int LABEL_KV_SIZE = 12; + +static const int LABEL_LEFT_SIZE = 100;// space + #.# MvB -- // +static const int LABEL_RIGHT_SIZE = 50; +static const int GRAPH_PADDING_LEFT = 0+LABEL_LEFT_SIZE; +static const int GRAPH_PADDING_RIGHT = 0+LABEL_RIGHT_SIZE; +static const int GRAPH_PADDING_TOP = 0;//10; +static const int GRAPH_PADDING_TOP_LABEL = 10; +static const int GRAPH_PADDING_BOTTOM = 0;//20; + +const static std::vector colors = { + +QColor(212,29, 97,255), //0-1 +QColor(140,43,168,255), //1-2 +QColor(93, 58,175,255), //2-3 +QColor(57, 76,169,255), //3-4 +QColor(40,138,226,255), //4-5 +QColor(30,157,227,255), //5-6 +QColor(34,172,192,255), //6-8 +QColor(25,137,123,255), //8-10 +QColor(74,159, 75,255), //10-12 +QColor(127,178,72,255), //12-15 +QColor(192,201,64,255), //15-20 +QColor(252,214,69,255), //20-30 +QColor(253,178,39,255), //30-40 +QColor(248,139,33,255), //40-50 +QColor(240,80, 42,255), //60-70 +QColor(108,76, 66,255), //70-80 +QColor(117,117,117,255),//80-90 +QColor(85,110,121,255), //100-125 +QColor(180,28, 34,255), //125-150 +QColor(134,17, 79,255), //175-200 +QColor(73, 27,138,255), //200-250 +QColor(48, 33,144,255), //250-300 +QColor(26, 39,124,255), //300-350 +QColor(18, 74,159,255), //350-400 +QColor(12, 89,153,255), //450-500 +QColor(14, 96,99,255), //500-550 +QColor(10, 77,64,255), //600-650 +QColor(33, 93,35,255), //700-750 + +}; diff --git a/src/qt/mempoolstats.cpp b/src/qt/mempoolstats.cpp new file mode 100644 index 00000000000..c15c7cf56a2 --- /dev/null +++ b/src/qt/mempoolstats.cpp @@ -0,0 +1,337 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include +#include +#include + +bool const MEMPOOL_DEBUG = true; + +MempoolStats::MempoolStats(QWidget *parent) : QWidget(parent) +{ + if (parent) { + parent->installEventFilter(this); + raise(); + } + setMouseTracking(true); + + // autoadjust font size + QGraphicsTextItem testText("jY"); //screendesign expected 27.5 pixel in width for this string + testText.setFont(QFont(LABEL_FONT, LABEL_TITLE_SIZE, QFont::Light)); + LABEL_TITLE_SIZE *= 27.5/testText.boundingRect().width(); + LABEL_KV_SIZE *= 27.5/testText.boundingRect().width(); + + m_gfx_view = new QGraphicsView(this); + m_scene = new QGraphicsScene(m_gfx_view); + m_gfx_view->setScene(m_scene); + m_gfx_view->setBackgroundBrush(QColor(16, 18, 31, 127)); + m_gfx_view->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); + if (m_clientmodel) + drawChart(); +} + +void MempoolStats::drawChart() +{ + if (!m_clientmodel) + return; + + m_scene->clear(); + + // + qreal current_x = GRAPH_PADDING_LEFT;//30+30 + + const qreal bottom = m_gfx_view->scene()->sceneRect().height()-GRAPH_PADDING_BOTTOM; + const qreal maxheight_g = (m_gfx_view->scene()->sceneRect().height()-GRAPH_PADDING_TOP-GRAPH_PADDING_TOP_LABEL-GRAPH_PADDING_BOTTOM); + if (MEMPOOL_DEBUG){ + LogPrintf("current_x = %s\n",current_x); + LogPrintf("bottom = %s\n",bottom); + LogPrintf("maxheight_g = %s\n",maxheight_g); + } + + std::vector fee_paths; + std::vector fee_subtotal_txcount; + size_t max_txcount=0; + QFont gridFont; + gridFont.setPointSize(12); + gridFont.setWeight(QFont::Bold); + int display_up_to_range = 0; + //let view touch boths sides//we will place an over lay of boxes + qreal maxwidth = m_gfx_view->scene()->sceneRect().width()-GRAPH_PADDING_LEFT-GRAPH_PADDING_RIGHT; + { + // we are going to access the clientmodel feehistogram directly avoiding a copy + QMutexLocker locker(&m_clientmodel->m_mempool_locker); + + size_t max_txcount_graph=0; + + if (m_clientmodel->m_mempool_feehist.size() == 0) { + // draw nothing + return; + } + + fee_subtotal_txcount.resize(m_clientmodel->m_mempool_feehist[0].second.size()); + // calculate max tx for upper bound of chart + for (const ClientModel::mempool_feehist_sample& sample : m_clientmodel->m_mempool_feehist) { + uint64_t txcount = 0; + int i = 0; + for (const interfaces::mempool_feeinfo& list_entry : sample.second) { + txcount += list_entry.tx_count; + fee_subtotal_txcount[i] += list_entry.tx_count; + i++; + } + if (txcount > max_txcount) max_txcount = txcount; + if (MEMPOOL_DEBUG){ + LogPrintf("txcount = %s\n",txcount); + LogPrintf("max_txcount = %s\n",max_txcount); + }} + + // hide ranges we don't have txns + for(size_t i = 0; i < fee_subtotal_txcount.size(); i++) { + if (fee_subtotal_txcount[i] > 0) { + display_up_to_range = i; + if (MEMPOOL_DEBUG){ + LogPrintf("display_up_to_range = %s\n",display_up_to_range); + } + }} + + // make a nice y-axis scale + const int amount_of_h_lines = 5; + if (max_txcount > 0) { + int val = qFloor(log10(1.0*max_txcount/amount_of_h_lines)); + int stepbase = qPow(10.0f, val); + int step = qCeil((1.0*max_txcount/amount_of_h_lines) / stepbase) * stepbase; + max_txcount_graph = step*amount_of_h_lines; + if (MEMPOOL_DEBUG){ + LogPrintf("val = %s\n",val); + LogPrintf("stepbase = %s\n",stepbase); + LogPrintf("step = %s\n",step); + LogPrintf("max_txcount_graph = %s\n",max_txcount_graph); + + }} + + // draw horizontal grid + if (MEMPOOL_DEBUG){ + LogPrintf("current_x = %s\n",current_x); + LogPrintf("bottom = %s\n",bottom); + } + QPainterPath tx_count_grid_path(QPointF(current_x, bottom)); + int bottomTxCount = 0; + for (int i=0; i < amount_of_h_lines; i++) + { + qreal lY = bottom-i*(maxheight_g/(amount_of_h_lines-1)); + tx_count_grid_path.moveTo(GRAPH_PADDING_LEFT, lY); + tx_count_grid_path.lineTo(GRAPH_PADDING_LEFT+maxwidth, lY); + + size_t grid_tx_count = (float)i*(max_txcount_graph-bottomTxCount)/(amount_of_h_lines-1) + bottomTxCount; + if (MEMPOOL_DEBUG){ + LogPrintf("grid_tx_count = %s\n",grid_tx_count); + } + + //QGraphicsTextItem *item_tx_count = m_scene->addText(QString::number(grid_tx_count), gridFont); + //item_tx_count->setPos(GRAPH_PADDING_LEFT+maxwidth, lY-(item_tx_count->boundingRect().height()/2)); + } + + QPen gridPen(QColor(57,59,69, 200), 0.75, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); + m_scene->addPath(tx_count_grid_path, gridPen); + + // draw fee ranges; + //QGraphicsTextItem *fee_range_title = m_scene->addText("Fee ranges\n(sat/b)", gridFont); + //fee_range_title->setPos(2, bottom+10); + + qreal c_y = bottom; + const qreal c_w = 10; + const qreal c_h = 10; + const qreal c_margin = 2; + c_y-=c_margin; + if (MEMPOOL_DEBUG){ + LogPrintf("c_y = %s\n",c_y); + } + int i = 0; + for (const interfaces::mempool_feeinfo& list_entry : m_clientmodel->m_mempool_feehist[0].second) { + if (i > display_up_to_range) { + continue; + } + ClickableRectItem *fee_rect = new ClickableRectItem(); + //(L, B, R, Top) + //fee_rect->setRect(10, c_y-7, c_w+100, c_h); + //neg moves it up + fee_rect->setRect(0, c_y-7, c_w, c_h); + + //Stack of rects on left + + QColor brush_color = colors[(i < static_cast(colors.size()) ? i : static_cast(colors.size())-1)]; + //brush_color.setAlpha(100); + brush_color.setAlpha(100); + if (m_selected_range >= 0 && m_selected_range != i) + { + // if one item is selected, hide out the other ones + // fee range boxes + brush_color.setAlpha(70);//not pressed + } + + fee_rect->setBrush(QBrush(brush_color)); + fee_rect->setPen(Qt::NoPen);//no outline only fill with QBrush + fee_rect->setCursor(Qt::PointingHandCursor); + connect(fee_rect, &ClickableRectItem::objectClicked, [this, i](QGraphicsItem*item) { + // if clicked, we select or deselect if selected + if (m_selected_range == i) { + m_selected_range = -1; + } else { + m_selected_range = i; + } + if (MEMPOOL_DEBUG){ + LogPrintf("m_selected_range = %s\n",m_selected_range); + } + drawChart(); + + }); + //m_scene->addItem(fee_rect); + + //TODO: fix bug/crash on click + //QGraphicsTextItem *fee_text = m_scene->addText("fee_text", gridFont); + //fee_text->setPlainText(QString::number(list_entry.fee_from)+"-"+QString::number(list_entry.fee_to)); + //if (i+1 == static_cast(m_clientmodel->m_mempool_feehist[0].second.size())) { + //if (i == static_cast(m_clientmodel->m_mempool_feehist[0].second.size())) { + // fee_text->setPlainText(QString::number(list_entry.fee_from)+"+"); + //} + //fee_text->setFont(gridFont); + //fee_text->setPos(4+c_w-7, c_y-7); + //m_scene->addItem(fee_text); + //m_scene->addItem(fee_rect); + + c_y-=c_h+c_margin; + i++; + } + + // calculate the x axis step per sample + // we ignore the time difference of collected samples due to locking issues + const qreal x_increment = 1.0 * (width()-GRAPH_PADDING_LEFT-GRAPH_PADDING_RIGHT) / m_clientmodel->m_mempool_max_samples; //540/clientmodel.h + if (MEMPOOL_DEBUG){ + LogPrintf("x_increment = %s\n",x_increment); + } + + // draw the paths + bool first = true; + for (const ClientModel::mempool_feehist_sample& sample : m_clientmodel->m_mempool_feehist) + { + current_x += x_increment; + int i = 0; + qreal y = bottom; + if (MEMPOOL_DEBUG){ + LogPrintf("y = %s\n",y); + LogPrintf("current_x = %s\n",current_x); + } + for (const interfaces::mempool_feeinfo& list_entry : sample.second) + { + if (i > display_up_to_range) + { + // skip ranges without txns + continue; + } + y -= (maxheight_g / max_txcount_graph * list_entry.tx_count); + if (first) + { + // first sample, initiate the path with first point + fee_paths.emplace_back(QPointF(current_x, y)); + } + else { fee_paths[i].lineTo(current_x, y); } + if (MEMPOOL_DEBUG){ + LogPrintf("y = %s\n",y); + LogPrintf("current_x = %s\n",current_x); + } + i++; + } + first = false; + }//end for loop + } // release lock for the actual drawing + + int i = 0; + QString total_text = tr("Last %1 hours").arg(QString::number(m_clientmodel->m_mempool_max_samples*m_clientmodel->m_mempool_collect_intervall/3600)); + for (auto feepath : fee_paths) { + // close paths + if (i > 0) { + feepath.lineTo(fee_paths[i-1].currentPosition()); + feepath.connectPath(fee_paths[i-1].toReversed()); + } else { + feepath.lineTo(current_x, bottom); + feepath.lineTo(GRAPH_PADDING_LEFT, bottom); + } + QColor pen_color = colors[(i < static_cast(colors.size()) ? i : static_cast(colors.size())-1)]; + QColor brush_color = pen_color; + //mempool paths + pen_color.setAlpha(100); + brush_color.setAlpha(90); + if (m_selected_range >= 0 && m_selected_range != i) { + pen_color.setAlpha(90); + brush_color.setAlpha(85); + } + if (m_selected_range >= 0 && m_selected_range == i) { + total_text = "transactions in selected fee range: "+QString::number(fee_subtotal_txcount[i]); + } + QPen pen_blue(pen_color, 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); + m_scene->addPath(feepath, pen_blue, QBrush(brush_color)); + //LogPrintf("i = %s\n",i); + i++; + } + + //QGraphicsTextItem *item_tx_count = m_scene->addText(total_text, gridFont); + //item_tx_count->setPos(GRAPH_PADDING_LEFT+(maxwidth/2), bottom); + +}//end drawChart() + +// We override the virtual resizeEvent of the QWidget to adjust tables column +// sizes as the tables width is proportional to the dialogs width. +void MempoolStats::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + m_gfx_view->resize(size()); + m_gfx_view->scene()->setSceneRect( + //rect().left()/2, + 0, + //rect().top()/1, + 0, + rect().width()-GRAPH_PADDING_RIGHT, + std::max( + rect().width()/2, + rect().height()/2 + ) + ); + if (MEMPOOL_DEBUG){ + LogPrintf("rect().left()/2 = %s\n",rect().left()/2); + LogPrintf("rect().top()/1 = %s\n",rect().top()/1); + LogPrintf("rect().width()-GRAPH_PADDING_WIDTH = %s\n",rect().width()-GRAPH_PADDING_RIGHT); + LogPrintf("rect().width()/2 = %s\n",rect().width()/2); + LogPrintf("rect().height()/2 = %s\n",rect().height()/2); + } + drawChart(); +} + +void MempoolStats::showEvent(QShowEvent *event) +{ + QWidget::showEvent(event); + if (m_clientmodel) + drawChart(); +} + +void MempoolStats::setClientModel(ClientModel *model) +{ + m_clientmodel = model; + if (model) { + connect(model, &ClientModel::mempoolFeeHistChanged, this, &MempoolStats::drawChart); + drawChart(); + } +} + +void ClickableTextItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { Q_EMIT objectClicked(this); } +void ClickableRectItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { Q_EMIT objectClicked(this); } +void MempoolStats::mouseMoveEvent(QMouseEvent *event){ + + if (MEMPOOL_DEBUG){ + LogPrintf( "x() = %s\n",x() ); + LogPrintf( "y() = %s\n",y() ); + } + +} diff --git a/src/qt/mempoolstats.h b/src/qt/mempoolstats.h new file mode 100644 index 00000000000..272e4491c93 --- /dev/null +++ b/src/qt/mempoolstats.h @@ -0,0 +1,63 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_MEMPOOLSTATS_H +#define BITCOIN_QT_MEMPOOLSTATS_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +class ClientModel; + +class ClickableTextItem : public QObject, public QGraphicsSimpleTextItem +{ + Q_OBJECT +protected: + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; +Q_SIGNALS: + void objectClicked(QGraphicsItem*); +}; + +class ClickableRectItem : public QObject, public QGraphicsRectItem +{ + Q_OBJECT +protected: + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; +Q_SIGNALS: + void objectClicked(QGraphicsItem*); +}; + + +class MempoolStats : public QWidget +{ + Q_OBJECT + +public: + explicit MempoolStats(QWidget *parent = Q_NULLPTR); + void setClientModel(ClientModel *model); + +public Q_SLOTS: + void drawChart(); + +private: + ClientModel* m_clientmodel = Q_NULLPTR; + + QGraphicsView *m_gfx_view; + QGraphicsScene *m_scene; + + virtual void resizeEvent(QResizeEvent* event) override; + virtual void showEvent(QShowEvent* event) override; + virtual void mouseMoveEvent(QMouseEvent *event) override; + + int m_selected_range = -1; +}; + +#endif // BITCOIN_QT_MEMPOOLSTATS_H diff --git a/src/qt/res/fonts/NOTICE.md b/src/qt/res/fonts/NOTICE.md new file mode 100644 index 00000000000..47a5c701b43 --- /dev/null +++ b/src/qt/res/fonts/NOTICE.md @@ -0,0 +1,7 @@ +LICENSE + +These fonts are licensed under the [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0). + +You can use them freely in your products & projects - print or digital, commercial or otherwise. + +This isn't legal advice, please consider consulting a lawyer and see the full license for all details. \ No newline at end of file diff --git a/src/qt/res/fonts/RobotoMono-Regular.ttf b/src/qt/res/fonts/RobotoMono-Regular.ttf new file mode 100644 index 00000000000..7c4ce36a442 Binary files /dev/null and b/src/qt/res/fonts/RobotoMono-Regular.ttf differ diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 0c3332ab761..f2e28953473 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -661,6 +661,7 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_ } ui->trafficGraph->setClientModel(model); + ui->mempool_graph->setClientModel(model); if (model && clientModel->getPeerTableModel() && clientModel->getBanTableModel()) { // Keep up to date with client setNumConnections(model->getNumConnections()); @@ -866,7 +867,11 @@ void RPCConsole::clear(bool keep_prompt) } // Set default style sheet +#ifdef Q_OS_MAC + QFontInfo fixedFontInfo(GUIUtil::fixedPitchFont(/*use_embedded_font=*/true)); +#else QFontInfo fixedFontInfo(GUIUtil::fixedPitchFont()); +#endif ui->messagesWidget->document()->setDefaultStyleSheet( QString( "table { }" @@ -1344,10 +1349,11 @@ QString RPCConsole::tabTitle(TabTypes tab_type) const QKeySequence RPCConsole::tabShortcut(TabTypes tab_type) const { switch (tab_type) { - case TabTypes::INFO: return QKeySequence(Qt::CTRL + Qt::Key_I); - case TabTypes::CONSOLE: return QKeySequence(Qt::CTRL + Qt::Key_T); - case TabTypes::GRAPH: return QKeySequence(Qt::CTRL + Qt::Key_N); - case TabTypes::PEERS: return QKeySequence(Qt::CTRL + Qt::Key_P); + case TabTypes::INFO: return QKeySequence(Qt::CTRL + Qt::Key_1); + case TabTypes::CONSOLE: return QKeySequence(Qt::CTRL + Qt::Key_2); + case TabTypes::MEMPOOL: return QKeySequence(Qt::CTRL + Qt::Key_3); + case TabTypes::GRAPH: return QKeySequence(Qt::CTRL + Qt::Key_4); + case TabTypes::PEERS: return QKeySequence(Qt::CTRL + Qt::Key_5); } // no default case, so the compiler can warn about missing cases assert(false); diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 2412ae543c8..52bf063202f 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -7,6 +7,7 @@ #include #include +#include #include @@ -63,11 +64,12 @@ class RPCConsole: public QWidget enum class TabTypes { INFO, CONSOLE, + MEMPOOL, GRAPH, PEERS }; - std::vector tabs() const { return {TabTypes::INFO, TabTypes::CONSOLE, TabTypes::GRAPH, TabTypes::PEERS}; } + std::vector tabs() const { return {TabTypes::INFO, TabTypes::CONSOLE, TabTypes::MEMPOOL, TabTypes::GRAPH, TabTypes::PEERS}; } QString tabTitle(TabTypes tab_type) const; QKeySequence tabShortcut(TabTypes tab_type) const; diff --git a/src/qt/trafficgraphwidget.cpp b/src/qt/trafficgraphwidget.cpp index 7e12410c801..d552148ad8b 100644 --- a/src/qt/trafficgraphwidget.cpp +++ b/src/qt/trafficgraphwidget.cpp @@ -50,19 +50,28 @@ int TrafficGraphWidget::getGraphRangeMins() const void TrafficGraphWidget::paintPath(QPainterPath &path, QQueue &samples) { int sampleCount = samples.size(); - if(sampleCount > 0) { + if(sampleCount > 0 && fMax > 0) { int h = height() - YMARGIN * 2, w = width() - XMARGIN * 2; int x = XMARGIN + w; path.moveTo(x, YMARGIN + h); for(int i = 0; i < sampleCount; ++i) { x = XMARGIN + w - w * i / DESIRED_SAMPLES; - int y = YMARGIN + h - (int)(h * samples.at(i) / fMax); + int y = YMARGIN + h - (int)(h * 1.0 * (fToggle ? (pow(samples.at(i), 0.3) / pow(fMax, 0.3)) : (samples.at(i) / fMax))); path.lineTo(x, y); } path.lineTo(x, YMARGIN + h); } } +void TrafficGraphWidget::mousePressEvent(QMouseEvent *event) +{ + QWidget::mousePressEvent(event); + fToggle = !fToggle; + timer->stop(); + timer->setInterval(timer->interval()); + timer->start(); +} + void TrafficGraphWidget::paintEvent(QPaintEvent *) { QPainter painter(this); @@ -82,28 +91,28 @@ void TrafficGraphWidget::paintEvent(QPaintEvent *) const QString units = tr("kB/s"); const float yMarginText = 2.0; - // draw lines - painter.setPen(axisCol); - painter.drawText(XMARGIN, YMARGIN + h - h * val / fMax-yMarginText, QString("%1 %2").arg(val).arg(units)); - for(float y = val; y < fMax; y += val) { - int yy = YMARGIN + h - h * y / fMax; - painter.drawLine(XMARGIN, yy, width() - XMARGIN, yy); - } - // if we drew 3 or fewer lines, break them up at the next lower order of magnitude - if(fMax / val <= 3.0f) { - axisCol = axisCol.darker(); + // if we drew 10 or 3 fewer lines, break them up at the next lower order of magnitude + if(fMax / val <= (fToggle ? 10.0f : 3.0f)) { + float oldval = val; val = pow(10.0f, base - 1); - painter.setPen(axisCol); - painter.drawText(XMARGIN, YMARGIN + h - h * val / fMax-yMarginText, QString("%1 %2").arg(val).arg(units)); + painter.setPen(axisCol.darker()); + painter.drawText(XMARGIN, YMARGIN + h - (h * 1.0 * (fToggle ? (pow(val, 0.3) / pow(fMax, 0.3)) : (val / fMax)))-yMarginText, QString("%1 %2").arg(val).arg(units)); int count = 1; - for(float y = val; y < fMax; y += val, count++) { - // don't overwrite lines drawn above + for(float y = val; y < (fToggle ? oldval : fMax); y += val, count++) { if(count % 10 == 0) continue; - int yy = YMARGIN + h - h * y / fMax; + int yy = YMARGIN + h - (h * 1.0 * (fToggle ? (pow(y, 0.3) / pow(fMax, 0.3)) : (y / fMax))); painter.drawLine(XMARGIN, yy, width() - XMARGIN, yy); } + val = oldval; + } + // draw lines + painter.setPen(axisCol); + for(float y = val; y < fMax; y += val) { + int yy = YMARGIN + h - (h * 1.0 * (fToggle ? (pow(y, 0.3) / pow(fMax, 0.3)) : (y / fMax))); + painter.drawLine(XMARGIN, yy, width() - XMARGIN, yy); } + painter.drawText(XMARGIN, YMARGIN + h - (h * 1.0 * (fToggle ? (pow(val, 0.3) / pow(fMax, 0.3)) : (val / fMax)))-yMarginText, QString("%1 %2").arg(val).arg(units)); painter.setRenderHint(QPainter::Antialiasing); if(!vSamplesIn.empty()) { diff --git a/src/qt/trafficgraphwidget.h b/src/qt/trafficgraphwidget.h index 2d8c825815a..95ee4c179ca 100644 --- a/src/qt/trafficgraphwidget.h +++ b/src/qt/trafficgraphwidget.h @@ -26,6 +26,8 @@ class TrafficGraphWidget : public QWidget protected: void paintEvent(QPaintEvent *) override; + void mousePressEvent(QMouseEvent *event) override; + bool fToggle = true; public Q_SLOTS: void updateRates();