diff options
author | Shawn Rutledge <shawn.rutledge@qt.io> | 2017-04-10 12:44:20 +0200 |
---|---|---|
committer | Edward Welbourne <edward.welbourne@qt.io> | 2017-06-20 09:53:14 +0000 |
commit | 9d23aebb271ea534a66cb3aceb2e63d9a1c870d6 (patch) | |
tree | a78a9b2739aabc594e04cb73cae3a81a24c54dd1 | |
parent | ccca8c94359e7f07333b710119bfdf00b25b78fd (diff) |
Add QLocale::formattedDataSize and consolidate use cases
It should be easier to translate sizes in bytes to human-readable
strings consistently rather than having to repeat this code (and the
string translations) in various places. The FileDialog in QtQuick.Controls
has a use for this, too.
[ChangeLog][QtCore][QLocale] Added QLocale::formattedDataSize() for
formatting quantities of bytes as kB, MB, GB etc.
Done-with: Edward Welbourne <edward.welbourne@qt.io>
Change-Id: I27bca146c3eba90fa7a5d52ef6626ce85723e3f0
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
-rw-r--r-- | examples/widgets/itemviews/storageview/storagemodel.cpp | 30 | ||||
-rw-r--r-- | src/corelib/tools/qlocale.cpp | 71 | ||||
-rw-r--r-- | src/corelib/tools/qlocale.h | 15 | ||||
-rw-r--r-- | src/widgets/dialogs/qfilesystemmodel.cpp | 16 | ||||
-rw-r--r-- | src/widgets/itemviews/qdirmodel.cpp | 17 | ||||
-rw-r--r-- | tests/auto/corelib/tools/qlocale/tst_qlocale.cpp | 78 |
6 files changed, 174 insertions, 53 deletions
diff --git a/examples/widgets/itemviews/storageview/storagemodel.cpp b/examples/widgets/itemviews/storageview/storagemodel.cpp index b7c594f8f7..1395c9f208 100644 --- a/examples/widgets/itemviews/storageview/storagemodel.cpp +++ b/examples/widgets/itemviews/storageview/storagemodel.cpp @@ -52,25 +52,10 @@ #include "storagemodel.h" #include <QDir> +#include <QLocale> #include <qmath.h> #include <cmath> -static QString sizeToString(qint64 size) -{ - static const char *const strings[] = { "b", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" }; - - if (size <= 0) - return StorageModel::tr("0 b"); - - double power = std::log((double)size)/std::log(1024.0); - int intPower = (int)power; - intPower = intPower >= 8 ? 8 - 1 : intPower; - - double normSize = size / std::pow(1024.0, intPower); - //: this should expand to "1.23 GB" - return StorageModel::tr("%1 %2").arg(normSize, 0, 'f', intPower > 0 ? 2 : 0).arg(strings[intPower]); -} - StorageModel::StorageModel(QObject *parent) : QAbstractTableModel(parent), m_volumes(QStorageInfo::mountedVolumes()) @@ -106,11 +91,11 @@ QVariant StorageModel::data(const QModelIndex &index, int role) const case ColumnFileSystemName: return volume.fileSystemType(); case ColumnTotal: - return sizeToString(volume.bytesTotal()); + return QLocale().formattedDataSize(volume.bytesTotal()); case ColumnFree: - return sizeToString(volume.bytesFree()); + return QLocale().formattedDataSize(volume.bytesFree()); case ColumnAvailable: - return sizeToString(volume.bytesAvailable()); + return QLocale().formattedDataSize(volume.bytesAvailable()); case ColumnIsReady: return volume.isReady(); case ColumnIsReadOnly: @@ -121,6 +106,7 @@ QVariant StorageModel::data(const QModelIndex &index, int role) const break; } } else if (role == Qt::ToolTipRole) { + QLocale locale; const QStorageInfo &volume = m_volumes.at(index.row()); return tr("Root path : %1\n" "Name: %2\n" @@ -140,9 +126,9 @@ QVariant StorageModel::data(const QModelIndex &index, int role) const arg(volume.displayName()). arg(QString::fromUtf8(volume.device())). arg(QString::fromUtf8(volume.fileSystemType())). - arg(sizeToString(volume.bytesTotal())). - arg(sizeToString(volume.bytesFree())). - arg(sizeToString(volume.bytesAvailable())). + arg(locale.formattedDataSize(volume.bytesTotal())). + arg(locale.formattedDataSize(volume.bytesFree())). + arg(locale.formattedDataSize(volume.bytesAvailable())). arg(volume.isReady() ? tr("true") : tr("false")). arg(volume.isReadOnly() ? tr("true") : tr("false")). arg(volume.isValid() ? tr("true") : tr("false")). diff --git a/src/corelib/tools/qlocale.cpp b/src/corelib/tools/qlocale.cpp index 5557b5af2d..43d83db835 100644 --- a/src/corelib/tools/qlocale.cpp +++ b/src/corelib/tools/qlocale.cpp @@ -61,6 +61,7 @@ #include "qvariant.h" #include "qstringbuilder.h" #include "private/qnumeric_p.h" +#include <cmath> #ifdef Q_OS_WIN # include <qt_windows.h> # include <time.h> @@ -3781,6 +3782,76 @@ QString QLocale::toCurrencyString(double value, const QString &symbol, int preci } /*! + \since 5.10 + + \enum QLocale::DataSizeFormat + + Specifies the format for representation of data quantities. + + \omitvalue DataSizeBase1000 + \omitvalue DataSizeSIQuantifiers + \value DataSizeIecFormat format using base 1024 and IEC prefixes: KiB, MiB, GiB, ... + \value DataSizeTraditionalFormat format using base 1024 and SI prefixes: kB, MB, GB, ... + \value DataSizeSIFormat format using base 1000 and SI prefixes: kB, MB, GB, ... + + \sa formattedDataSize() +*/ + +/*! + \since 5.10 + + Converts a size in bytes to a human-readable localized string, expressed in + a unit for which the numeric portion is at least 1 but as low as + possible. For example if \a bytes is 16384, \a precision is 2, and \a format + is \c DataSizeIecFormat (the default), this function returns "16.00 KiB"; + for 1330409069609 bytes it returns "1.21 GiB"; and so on. If \a format is \c + DataSizeIecFormat or \c DataSizeTraditionalFormat, the given number of bytes + is divided by a power of 1024, with result less than 1024; for \c + DataSizeSIFormat, it is divided by a power of 1000, with result less than + 1000. DataSizeIecFormat uses the new IEC standard quantifiers Ki, Mi and so + on, whereas DataSizeSIFormat uses and DataSizeTraditionalFormat abuses the + older SI quantifiers k, M, etc. + + \sa refresh(), caching() +*/ +QString QLocale::formattedDataSize(qint64 bytes, int precision, DataSizeFormats format) +{ + int power, base = 1000; + if (!bytes) { + power = 0; + } else if (format & DataSizeBase1000) { + power = int(std::log10(qAbs(bytes)) / 3); + } else { // Compute log2(bytes) / 10: + power = int((63 - qCountLeadingZeroBits(quint64(qAbs(bytes)))) / 10); + base = 1024; + } + // Only go to doubles if we'll be using a quantifier: + const QString number = power + ? toString(bytes / std::pow(double(base), power), 'f', qMin(precision, 3 * power)) + : toString(bytes); + + // We don't support sizes in units larger than exbibytes because + // the number of bytes would not fit into qint64. + Q_ASSERT(power <= 6 && power >= 0); + QString unit; + if (power > 0) { + quint16 index, size; + if (format & DataSizeSIQuantifiers) { + index = d->m_data->m_byte_si_quantified_idx; + size = d->m_data->m_byte_si_quantified_size; + } else { + index = d->m_data->m_byte_iec_quantified_idx; + size = d->m_data->m_byte_iec_quantified_size; + } + unit = getLocaleListData(byte_unit_data + index, size, power - 1); + } else { + unit = getLocaleData(byte_unit_data + d->m_data->m_byte_idx, d->m_data->m_byte_size); + } + + return number + QLatin1Char(' ') + unit; +} + +/*! \since 4.8 Returns an ordered list of locale names for translation purposes in diff --git a/src/corelib/tools/qlocale.h b/src/corelib/tools/qlocale.h index f9482a7e3a..54b1a32946 100644 --- a/src/corelib/tools/qlocale.h +++ b/src/corelib/tools/qlocale.h @@ -913,6 +913,19 @@ public: CurrencyDisplayName }; + enum DataSizeFormat { + // Single-bit values, for internal use. + DataSizeBase1000 = 1, // use factors of 1000 instead of IEC's 1024; + DataSizeSIQuantifiers = 2, // use SI quantifiers instead of IEC ones. + + // Flags values for use in API: + DataSizeIecFormat = 0, // base 1024, KiB, MiB, GiB, ... + DataSizeTraditionalFormat = DataSizeSIQuantifiers, // base 1024, kB, MB, GB, ... + DataSizeSIFormat = DataSizeBase1000 | DataSizeSIQuantifiers // base 1000, kB, MB, GB, ... + }; + Q_DECLARE_FLAGS(DataSizeFormats, DataSizeFormat) + Q_FLAG(DataSizeFormats) + QLocale(); QLocale(const QString &name); QLocale(Language language, Country country = AnyCountry); @@ -1045,6 +1058,8 @@ public: { return toCurrencyString(double(i), symbol, precision); } #endif + QString formattedDataSize(qint64 bytes, int precision = 2, DataSizeFormats format = DataSizeIecFormat); + QStringList uiLanguages() const; bool operator==(const QLocale &other) const; diff --git a/src/widgets/dialogs/qfilesystemmodel.cpp b/src/widgets/dialogs/qfilesystemmodel.cpp index db1ce3fe0e..45a6b4f8a0 100644 --- a/src/widgets/dialogs/qfilesystemmodel.cpp +++ b/src/widgets/dialogs/qfilesystemmodel.cpp @@ -774,21 +774,7 @@ QString QFileSystemModelPrivate::size(const QModelIndex &index) const QString QFileSystemModelPrivate::size(qint64 bytes) { - // According to the Si standard KB is 1000 bytes, KiB is 1024 - // but on windows sizes are calculated by dividing by 1024 so we do what they do. - const qint64 kb = 1024; - const qint64 mb = 1024 * kb; - const qint64 gb = 1024 * mb; - const qint64 tb = 1024 * gb; - if (bytes >= tb) - return QFileSystemModel::tr("%1 TB").arg(QLocale().toString(qreal(bytes) / tb, 'f', 3)); - if (bytes >= gb) - return QFileSystemModel::tr("%1 GB").arg(QLocale().toString(qreal(bytes) / gb, 'f', 2)); - if (bytes >= mb) - return QFileSystemModel::tr("%1 MB").arg(QLocale().toString(qreal(bytes) / mb, 'f', 1)); - if (bytes >= kb) - return QFileSystemModel::tr("%1 KB").arg(QLocale().toString(bytes / kb)); - return QFileSystemModel::tr("%1 bytes").arg(QLocale().toString(bytes)); + return QLocale::system().formattedDataSize(bytes); } /*! diff --git a/src/widgets/itemviews/qdirmodel.cpp b/src/widgets/itemviews/qdirmodel.cpp index 95e6a1840d..9d2e69dec9 100644 --- a/src/widgets/itemviews/qdirmodel.cpp +++ b/src/widgets/itemviews/qdirmodel.cpp @@ -1308,22 +1308,7 @@ QString QDirModelPrivate::size(const QModelIndex &index) const // Nautilus - "9 items" (the number of children) } - // According to the Si standard KB is 1000 bytes, KiB is 1024 - // but on windows sizes are calulated by dividing by 1024 so we do what they do. - const quint64 kb = 1024; - const quint64 mb = 1024 * kb; - const quint64 gb = 1024 * mb; - const quint64 tb = 1024 * gb; - quint64 bytes = n->info.size(); - if (bytes >= tb) - return QFileSystemModel::tr("%1 TB").arg(QLocale().toString(qreal(bytes) / tb, 'f', 3)); - if (bytes >= gb) - return QFileSystemModel::tr("%1 GB").arg(QLocale().toString(qreal(bytes) / gb, 'f', 2)); - if (bytes >= mb) - return QFileSystemModel::tr("%1 MB").arg(QLocale().toString(qreal(bytes) / mb, 'f', 1)); - if (bytes >= kb) - return QFileSystemModel::tr("%1 KB").arg(QLocale().toString(bytes / kb)); - return QFileSystemModel::tr("%1 byte(s)").arg(QLocale().toString(bytes)); + return QLocale::system().formattedDataSize(n->info.size()); } QString QDirModelPrivate::type(const QModelIndex &index) const diff --git a/tests/auto/corelib/tools/qlocale/tst_qlocale.cpp b/tests/auto/corelib/tools/qlocale/tst_qlocale.cpp index 2c342dcfe6..c48f0e24eb 100644 --- a/tests/auto/corelib/tools/qlocale/tst_qlocale.cpp +++ b/tests/auto/corelib/tools/qlocale/tst_qlocale.cpp @@ -137,6 +137,9 @@ private slots: void textDirection_data(); void textDirection(); + void formattedDataSize_data(); + void formattedDataSize(); + private: QString m_decimal, m_thousand, m_sdate, m_ldate, m_time; QString m_sysapp; @@ -2473,5 +2476,80 @@ void tst_QLocale::textDirection() QCOMPARE(locale.textDirection() == Qt::RightToLeft, rightToLeft); } +void tst_QLocale::formattedDataSize_data() +{ + QTest::addColumn<QLocale::Language>("language"); + QTest::addColumn<int>("decimalPlaces"); + QTest::addColumn<QLocale::DataSizeFormats>("units"); + QTest::addColumn<int>("bytes"); + QTest::addColumn<QString>("output"); + + struct { + const char *name; + QLocale::Language lang; + const char *bytes; + const char abbrev; + const char sep; // decimal separator + } data[] = { + { "English", QLocale::English, "bytes", 'B', '.' }, + { "French", QLocale::French, "octets", 'o', ',' }, + { "C", QLocale::C, "bytes", 'B', '.' } + }; + + for (const auto row : data) { +#define ROWB(id, deci, num, text) \ + QTest::addRow("%s-%s", row.name, id) \ + << row.lang << deci << format \ + << num << (QString(text) + QChar(' ') + QString(row.bytes)) +#define ROWQ(id, deci, num, head, tail) \ + QTest::addRow("%s-%s", row.name, id) \ + << row.lang << deci << format \ + << num << (QString(head) + QChar(row.sep) + QString(tail) + QChar(row.abbrev)) + + // Metatype system fails to handle raw enum members as format; needs variable + { + const QLocale::DataSizeFormats format = QLocale::DataSizeIecFormat; + ROWB("IEC-0", 2, 0, "0"); + ROWB("IEC-10", 2, 10, "10"); + ROWQ("IEC-12Ki", 2, 12345, "12", "06 Ki"); + ROWQ("IEC-16Ki", 2, 16384, "16", "00 Ki"); + ROWQ("IEC-1235k", 2, 1234567, "1", "18 Mi"); + ROWQ("IEC-1374k", 2, 1374744, "1", "31 Mi"); + ROWQ("IEC-1234M", 2, 1234567890, "1", "15 Gi"); + } + { + const QLocale::DataSizeFormats format = QLocale::DataSizeTraditionalFormat; + ROWB("Trad-0", 2, 0, "0"); + ROWB("Trad-10", 2, 10, "10"); + ROWQ("Trad-12Ki", 2, 12345, "12", "06 k"); + ROWQ("Trad-16Ki", 2, 16384, "16", "00 k"); + ROWQ("Trad-1235k", 2, 1234567, "1", "18 M"); + ROWQ("Trad-1374k", 2, 1374744, "1", "31 M"); + ROWQ("Trad-1234M", 2, 1234567890, "1", "15 G"); + } + { + const QLocale::DataSizeFormats format = QLocale::DataSizeSIFormat; + ROWB("Decimal-0", 2, 0, "0"); + ROWB("Decimal-10", 2, 10, "10"); + ROWQ("Decimal-16Ki", 2, 16384, "16", "38 k"); + ROWQ("Decimal-1234k", 2, 1234567, "1", "23 M"); + ROWQ("Decimal-1374k", 2, 1374744, "1", "37 M"); + ROWQ("Decimal-1234M", 2, 1234567890, "1", "23 G"); + } +#undef ROWQ +#undef ROWB + } +} + +void tst_QLocale::formattedDataSize() +{ + QFETCH(QLocale::Language, language); + QFETCH(int, decimalPlaces); + QFETCH(QLocale::DataSizeFormats, units); + QFETCH(int, bytes); + QFETCH(QString, output); + QCOMPARE(QLocale(language).formattedDataSize(bytes, decimalPlaces, units), output); +} + QTEST_MAIN(tst_QLocale) #include "tst_qlocale.moc" |