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>
This commit is contained in:
Shawn Rutledge 2017-04-10 12:44:20 +02:00 committed by Edward Welbourne
parent ccca8c9435
commit 9d23aebb27
6 changed files with 174 additions and 53 deletions

View File

@ -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")).

View File

@ -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>
@ -3780,6 +3781,76 @@ QString QLocale::toCurrencyString(double value, const QString &symbol, int preci
return format.arg(str, sym);
}
/*!
\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

View File

@ -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;

View File

@ -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);
}
/*!

View File

@ -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

View File

@ -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"