From 5bdd3fa95d023135e1646a1224bb4ed98c901dbf Mon Sep 17 00:00:00 2001 From: eelke Date: Fri, 12 Apr 2024 13:26:10 +0200 Subject: [PATCH] Improve conversion of bytes to human readable string Fixes issues like showing 0 MiB when the value is just slightly less then 1 MiB. --- pglab/util/PgLabItemDelegate.cpp | 73 ++++++++++----------- pglab/util/PgLabItemDelegate.h | 3 +- pglablib/pglablib.pro | 8 ++- pglablib/utils/HumanReadableBytes.cpp | 56 ++++++++++++++++ pglablib/utils/HumanReadableBytes.h | 5 ++ tests/pglabtests/pglabtests.pro | 1 + tests/pglabtests/tst_HumanReadableBytes.cpp | 23 +++++++ 7 files changed, 125 insertions(+), 44 deletions(-) create mode 100644 pglablib/utils/HumanReadableBytes.cpp create mode 100644 pglablib/utils/HumanReadableBytes.h create mode 100644 tests/pglabtests/tst_HumanReadableBytes.cpp diff --git a/pglab/util/PgLabItemDelegate.cpp b/pglab/util/PgLabItemDelegate.cpp index b49e079..d51f7b0 100644 --- a/pglab/util/PgLabItemDelegate.cpp +++ b/pglab/util/PgLabItemDelegate.cpp @@ -5,6 +5,7 @@ #include "ResultTableModelUtil.h" #include "CustomDataRole.h" #include "AbstractEditorFactory.h" +#include "utils/HumanReadableBytes.h" PgLabItemDelegate::PgLabItemDelegate(QObject *parent) : QStyledItemDelegate(parent) @@ -111,37 +112,11 @@ void PgLabItemDelegate::initStyleOption(QStyleOptionViewItem *option, else { forground_color = GetDefaultColorForType(oid); if (meaning == DataMeaning::Bytes) { - QString suffix; - auto s = value.toLongLong(); - double val; - if (s > 1024 * 1024 * 1000) { - val = s / (1024 * 1024 * 1024); - suffix = "GiB"; - forground_color = QColorConstants::Svg::darkorange; - option->font.setBold(true); - } - else if (s > 1024 * 1000) { - val = s / (1024 * 1024); - suffix = "MiB"; - forground_color = QColorConstants::Svg::darkgoldenrod; - } - else if (s > 1000) { - val = s / 1024; - suffix = "KiB"; - forground_color = QColorConstants::Svg::darkgreen; - } - else { - val = s; - suffix = "B"; - forground_color = QColorConstants::Svg::darkblue; - } - option->text = QString{ "%1 %2" }.arg(val, 0, 'g', 3).arg(suffix) ; + option->text = HandleBytes(value.toLongLong(), forground_color); } else { auto str = value.toString(); auto s = str.left(100); - // auto f = s.indexOf('\n'); - // option->text = ((f > 0) ? s.left(f) : s).toString(); option->text = s; } } @@ -161,30 +136,48 @@ void PgLabItemDelegate::initStyleOption(QStyleOptionViewItem *option, option->styleObject = nullptr; } -void PgLabItemDelegate::paint(QPainter *painter, - const QStyleOptionViewItem &option, const QModelIndex &index) const +QString PgLabItemDelegate::HandleBytes(qlonglong s, QColor &forground_color) const { - Q_ASSERT(index.isValid()); + auto str = HumanReadableBytes(s); + if (s > 1024 * 1024 * 1024) { + forground_color = QColorConstants::Svg::darkorange; + } + else if (s > 1024 * 1024) { + forground_color = QColorConstants::Svg::darkgoldenrod; + } + else if (s > 1024) { + forground_color = QColorConstants::Svg::darkgreen; + } + else { + forground_color = QColorConstants::Svg::darkblue; + } + return QString::fromStdString(str); +} - QStyleOptionViewItem opt = option; - initStyleOption(&opt, index); +void PgLabItemDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + Q_ASSERT(index.isValid()); - const QWidget *widget = option.widget; // QStyledItemDelegatePrivate::widget(option); - QStyle *style = widget ? widget->style() : QApplication::style(); - style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget); + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + + const QWidget *widget = option.widget; // QStyledItemDelegatePrivate::widget(option); + QStyle *style = widget ? widget->style() : QApplication::style(); + style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget); } QWidget *PgLabItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { - if (m_editorFactory) - return m_editorFactory->createEditor(parent, option, index); - return QStyledItemDelegate::createEditor(parent, option, index); + if (m_editorFactory) + return m_editorFactory->createEditor(parent, option, index); + return QStyledItemDelegate::createEditor(parent, option, index); } void PgLabItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { - if (m_editorFactory) - m_editorFactory->setEditorData(editor, index); + if (m_editorFactory) + m_editorFactory->setEditorData(editor, index); else QStyledItemDelegate::setEditorData(editor, index); } diff --git a/pglab/util/PgLabItemDelegate.h b/pglab/util/PgLabItemDelegate.h index b5a26ea..4cdcbbb 100644 --- a/pglab/util/PgLabItemDelegate.h +++ b/pglab/util/PgLabItemDelegate.h @@ -29,7 +29,8 @@ protected: virtual void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const override; private: - AbstractEditorFactory *m_editorFactory = nullptr; + AbstractEditorFactory *m_editorFactory = nullptr; + QString HandleBytes(qlonglong s, QColor &forground_color) const; }; #endif // PGLABITEMDELEGATE_H diff --git a/pglablib/pglablib.pro b/pglablib/pglablib.pro index 876bd84..fd0c53a 100644 --- a/pglablib/pglablib.pro +++ b/pglablib/pglablib.pro @@ -87,7 +87,8 @@ SOURCES += \ catalog/PgLanguage.cpp \ catalog/PgAcl.cpp \ catalog/PgSequence.cpp \ - catalog/PgSequenceContainer.cpp + catalog/PgSequenceContainer.cpp \ + utils/HumanReadableBytes.cpp HEADERS += \ Pglablib.h \ @@ -119,7 +120,7 @@ HEADERS += \ ui/catalog/tables/TableNode.h \ ui/catalog/tables/TableSize.h \ ui/catalog/tables/TableTreeBuilder.h \ - util.h \ + util.h \ SqlFormattingUtils.h \ catalog/PgCatalogTypes.h \ catalog/PgKeywordList.h \ @@ -162,7 +163,8 @@ HEADERS += \ catalog/PgLanguage.h \ catalog/PgAcl.h \ catalog/PgSequence.h \ - catalog/PgSequenceContainer.h + catalog/PgSequenceContainer.h \ + utils/HumanReadableBytes.h unix { target.path = /usr/lib diff --git a/pglablib/utils/HumanReadableBytes.cpp b/pglablib/utils/HumanReadableBytes.cpp new file mode 100644 index 0000000..9294d11 --- /dev/null +++ b/pglablib/utils/HumanReadableBytes.cpp @@ -0,0 +1,56 @@ +#include "HumanReadableBytes.h" +#include +#include + +using namespace std; + +namespace { + +struct scale { + int64_t scale; + const char* prefix; +}; + +void fmt(std::ostringstream &o, double d) +{ + ; + if (d < 10.0) + { + o << fixed << setprecision(2) << d; + //return std::format("{:.3g}", d); + } + else if (d < 100.0) + { + o << fixed << setprecision(1) << d; + //return std::format("{:.3g}", d); + } + else + { + o << fixed << setprecision(0) << d; + } +} + +} + +std::string HumanReadableBytes(uint64_t bytes) +{ + static scale scales[] = { + { 1024ll * 1024 * 1024 * 1024, "Ti" }, + { 1024ll * 1024 * 1024, "Gi" }, + { 1024ll * 1024, "Mi" }, + { 1024ll, "ki" }, + }; + + std::ostringstream out; + for (int i = 0; i < (sizeof(scales) / sizeof(scale)); ++i) + { + if (bytes >= scales[i].scale) + { + fmt(out, bytes / double(scales[i].scale)); + out << " " << scales[i].prefix << "B"; + return out.str(); + } + } + out << bytes << " B"; + return out.str(); +} diff --git a/pglablib/utils/HumanReadableBytes.h b/pglablib/utils/HumanReadableBytes.h new file mode 100644 index 0000000..b76de05 --- /dev/null +++ b/pglablib/utils/HumanReadableBytes.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +std::string HumanReadableBytes(uint64_t bytes); diff --git a/tests/pglabtests/pglabtests.pro b/tests/pglabtests/pglabtests.pro index a5405b1..ae63f49 100644 --- a/tests/pglabtests/pglabtests.pro +++ b/tests/pglabtests/pglabtests.pro @@ -18,6 +18,7 @@ SOURCES += main.cpp \ tst_ConvertLangToSqlString.cpp \ tst_ConvertToMultiLineCString.cpp \ tst_ExplainJsonParser.cpp \ + tst_HumanReadableBytes.cpp \ tst_escapeConnectionStringValue.cpp \ tst_expected.cpp \ tst_SqlLexer.cpp \ diff --git a/tests/pglabtests/tst_HumanReadableBytes.cpp b/tests/pglabtests/tst_HumanReadableBytes.cpp new file mode 100644 index 0000000..88927d6 --- /dev/null +++ b/tests/pglabtests/tst_HumanReadableBytes.cpp @@ -0,0 +1,23 @@ +#include +#include +#include "PrintTo_Qt.h" + +#include "utils/HumanReadableBytes.h" + +TEST(HumanReadableBytesTest, bytes7) +{ + auto s = HumanReadableBytes(7); + ASSERT_EQ("7 B", s); +} + +TEST(HumanReadableBytesTest, bytes1023) +{ + auto s = HumanReadableBytes(1023); + ASSERT_EQ("1023 B", s); +} + +TEST(HumanReadableBytesTest, bytes1k) +{ + auto s = HumanReadableBytes(1024); + ASSERT_EQ("1.00 kiB", s); +}