| 1 | /* |
| 2 | SPDX-FileCopyrightText: 2018 Alexander Stippich <a.stippich@gmx.net> |
| 3 | |
| 4 | SPDX-License-Identifier: LGPL-2.1-or-later |
| 5 | */ |
| 6 | |
| 7 | #include "formatstrings_p.h" |
| 8 | |
| 9 | #include <math.h> |
| 10 | #include <QDateTime> |
| 11 | #include <KLocalizedString> |
| 12 | #include <KFormat> |
| 13 | |
| 14 | using namespace KFileMetaData; |
| 15 | |
| 16 | /* |
| 17 | * Calculates and returns the number of digits after the |
| 18 | * comma to always show three significant digits for use |
| 19 | * with KFormat::formatValue. |
| 20 | */ |
| 21 | int threeSignificantDigits(int value) |
| 22 | { |
| 23 | if (value == 0) { |
| 24 | return 0; |
| 25 | } |
| 26 | int before_decimal_point = static_cast<int>(log10(x: value > 0 ? value : -value)) % 3; |
| 27 | return 2 - before_decimal_point; |
| 28 | } |
| 29 | |
| 30 | QString FormatStrings::toStringFunction(const QVariant& value) |
| 31 | { |
| 32 | return value.toString(); |
| 33 | } |
| 34 | |
| 35 | QString FormatStrings::formatDouble(const QVariant& value) |
| 36 | { |
| 37 | return QLocale().toString(f: value.toDouble(),format: 'g',precision: 3); |
| 38 | } |
| 39 | |
| 40 | QString FormatStrings::formatDate(const QVariant& value) |
| 41 | { |
| 42 | KFormat form; |
| 43 | QDateTime dt; |
| 44 | if (value.userType() == QMetaType::QDateTime) { |
| 45 | dt = value.toDateTime(); |
| 46 | } else { |
| 47 | dt = QDateTime::fromString(string: value.toString(), format: Qt::ISODate); |
| 48 | } |
| 49 | if (dt.isValid()) { |
| 50 | return form.formatRelativeDateTime(dateTime: dt, format: QLocale::LongFormat); |
| 51 | } |
| 52 | return QString(); |
| 53 | } |
| 54 | |
| 55 | QString FormatStrings::formatDuration(const QVariant& value) |
| 56 | { |
| 57 | KFormat form; |
| 58 | return form.formatDuration(msecs: value.toInt() * 1000); |
| 59 | } |
| 60 | |
| 61 | QString FormatStrings::formatBitRate(const QVariant& value) |
| 62 | { |
| 63 | KFormat form; |
| 64 | return i18nc("@label bitrate (per second)" , "%1/s" , form.formatValue(value.toInt(), |
| 65 | KFormat::Unit::Bit, threeSignificantDigits(value.toInt()), KFormat::UnitPrefix::AutoAdjust, KFormat::MetricBinaryDialect)); |
| 66 | } |
| 67 | |
| 68 | QString FormatStrings::formatSampleRate(const QVariant& value) |
| 69 | { |
| 70 | KFormat form; |
| 71 | return form.formatValue(value: value.toInt(), unit: KFormat::Unit::Hertz, precision: threeSignificantDigits(value: value.toInt()), prefix: KFormat::UnitPrefix::AutoAdjust, dialect: KFormat::MetricBinaryDialect); |
| 72 | } |
| 73 | |
| 74 | QString FormatStrings::formatOrientationValue(const QVariant& value) |
| 75 | { |
| 76 | QString string; |
| 77 | switch (value.toInt()) { |
| 78 | case 1: string = i18nc("Description of image orientation" , "Unchanged" ); break; |
| 79 | case 2: string = i18nc("Description of image orientation" , "Horizontally flipped" ); break; |
| 80 | case 3: string = i18nc("Description of image orientation" , "180° rotated" ); break; |
| 81 | case 4: string = i18nc("Description of image orientation" , "Vertically flipped" ); break; |
| 82 | case 5: string = i18nc("Description of image orientation" , "Transposed" ); break; |
| 83 | case 6: string = i18nc("Description of image orientation, counter clock-wise rotated" , "90° rotated CCW " ); break; |
| 84 | case 7: string = i18nc("Description of image orientation" , "Transversed" ); break; |
| 85 | case 8: string = i18nc("Description of image orientation, counter clock-wise rotated" , "270° rotated CCW" ); break; |
| 86 | default: |
| 87 | break; |
| 88 | } |
| 89 | return string; |
| 90 | } |
| 91 | |
| 92 | QString FormatStrings::formatPhotoFlashValue(const QVariant& value) |
| 93 | { |
| 94 | // copied from exiv2 tags_int.cpp |
| 95 | const QMap<int, QString> flashTranslation = { |
| 96 | { 0x00, i18nc("Description of photo flash" , "No flash" ) }, |
| 97 | { 0x01, i18nc("Description of photo flash" , "Fired" ) }, |
| 98 | { 0x05, i18nc("Description of photo flash" , "Fired, return light not detected" ) }, |
| 99 | { 0x07, i18nc("Description of photo flash" , "Fired, return light detected" ) }, |
| 100 | { 0x08, i18nc("Description of photo flash" , "Yes, did not fire" ) }, |
| 101 | { 0x09, i18nc("Description of photo flash" , "Yes, compulsory" ) }, |
| 102 | { 0x0d, i18nc("Description of photo flash" , "Yes, compulsory, return light not detected" ) }, |
| 103 | { 0x0f, i18nc("Description of photo flash" , "Yes, compulsory, return light detected" ) }, |
| 104 | { 0x10, i18nc("Description of photo flash" , "No, compulsory" ) }, |
| 105 | { 0x14, i18nc("Description of photo flash" , "No, did not fire, return light not detected" ) }, |
| 106 | { 0x18, i18nc("Description of photo flash" , "No, auto" ) }, |
| 107 | { 0x19, i18nc("Description of photo flash" , "Yes, auto" ) }, |
| 108 | { 0x1d, i18nc("Description of photo flash" , "Yes, auto, return light not detected" ) }, |
| 109 | { 0x1f, i18nc("Description of photo flash" , "Yes, auto, return light detected" ) }, |
| 110 | { 0x20, i18nc("Description of photo flash" , "No flash function" ) }, |
| 111 | { 0x30, i18nc("Description of photo flash" , "No, no flash function" ) }, |
| 112 | { 0x41, i18nc("Description of photo flash" , "Yes, red-eye reduction" ) }, |
| 113 | { 0x45, i18nc("Description of photo flash" , "Yes, red-eye reduction, return light not detected" ) }, |
| 114 | { 0x47, i18nc("Description of photo flash" , "Yes, red-eye reduction, return light detected" ) }, |
| 115 | { 0x49, i18nc("Description of photo flash" , "Yes, compulsory, red-eye reduction" ) }, |
| 116 | { 0x4d, i18nc("Description of photo flash" , "Yes, compulsory, red-eye reduction, return light not detected" ) }, |
| 117 | { 0x4f, i18nc("Description of photo flash" , "Yes, compulsory, red-eye reduction, return light detected" ) }, |
| 118 | { 0x50, i18nc("Description of photo flash" , "No, red-eye reduction" ) }, |
| 119 | { 0x58, i18nc("Description of photo flash" , "No, auto, red-eye reduction" ) }, |
| 120 | { 0x59, i18nc("Description of photo flash" , "Yes, auto, red-eye reduction" ) }, |
| 121 | { 0x5d, i18nc("Description of photo flash" , "Yes, auto, red-eye reduction, return light not detected" ) }, |
| 122 | { 0x5f, i18nc("Description of photo flash" , "Yes, auto, red-eye reduction, return light detected" ) } |
| 123 | }; |
| 124 | if (flashTranslation.contains(key: value.toInt())) { |
| 125 | return flashTranslation.value(key: value.toInt()); |
| 126 | } else { |
| 127 | return i18n("Unknown" ); |
| 128 | } |
| 129 | |
| 130 | } |
| 131 | |
| 132 | QString FormatStrings::formatAsDegree(const QVariant& value) |
| 133 | { |
| 134 | return i18nc("Symbol of degree, no space" , "%1°" , QLocale().toString(value.toDouble())); |
| 135 | } |
| 136 | |
| 137 | QString FormatStrings::formatAsMeter(const QVariant& value) |
| 138 | { |
| 139 | KFormat form; |
| 140 | return form.formatValue(value: value.toDouble(), unit: KFormat::Unit::Meter, precision: 1, prefix: KFormat::UnitPrefix::AutoAdjust, dialect: KFormat::MetricBinaryDialect); |
| 141 | } |
| 142 | |
| 143 | QString FormatStrings::formatAsMilliMeter(const QVariant& value) |
| 144 | { |
| 145 | return i18nc("Focal length given in mm" , "%1 mm" , QLocale().toString(value.toDouble(), 'g', 3)); |
| 146 | } |
| 147 | |
| 148 | QString FormatStrings::formatAsFrameRate(const QVariant& value) |
| 149 | { |
| 150 | return i18nc("Symbol of frames per second, with space" , "%1 fps" , QLocale().toString(round(value.toDouble() * 100) / 100)); |
| 151 | } |
| 152 | |
| 153 | QString FormatStrings::formatPhotoTime(const QVariant& value) |
| 154 | { |
| 155 | auto val = value.toDouble(); |
| 156 | if (val < 0.3 && !qFuzzyIsNull(d: val)) { |
| 157 | auto reciprocal = 1.0/val; |
| 158 | auto roundedReciprocal = round(x: reciprocal); |
| 159 | if (abs(x: reciprocal - roundedReciprocal) < 1e-3) { |
| 160 | return i18nc("Time period given in seconds as rational number, denominator is given" , "1/%1 s" , roundedReciprocal); |
| 161 | } |
| 162 | } |
| 163 | return i18nc("Time period given in seconds" , "%1 s" , QLocale().toString(value.toDouble(), 'g', 3)); |
| 164 | } |
| 165 | |
| 166 | QString FormatStrings::formatPhotoExposureBias(const QVariant& value) |
| 167 | { |
| 168 | QLocale locale; |
| 169 | auto val = value.toDouble(); |
| 170 | /* |
| 171 | * Exposure values are mostly in steps of one half or third. |
| 172 | * Try to construct a rational number from it. |
| 173 | * Output as double when it is not possible. |
| 174 | */ |
| 175 | auto sixthParts = val * 6; |
| 176 | int roundedSixthParts = static_cast<int>(round(x: sixthParts)); |
| 177 | int fractional = roundedSixthParts % 6; |
| 178 | if (fractional == 0 || abs(x: sixthParts - roundedSixthParts) > 1e-3) { |
| 179 | return i18nc("Exposure bias/compensation in exposure value (EV)" , "%1 EV" , locale.toString(val, 'g', 3)); |
| 180 | } |
| 181 | int integral = roundedSixthParts / 6; |
| 182 | int nominator = fractional; |
| 183 | int denominator = 6; |
| 184 | if (nominator % 2 == 0) { |
| 185 | nominator = nominator / 2; |
| 186 | denominator = denominator / 2; |
| 187 | } else if (nominator % 3 == 0) { |
| 188 | nominator = nominator / 3; |
| 189 | denominator = denominator / 3; |
| 190 | } |
| 191 | if (integral != 0) { |
| 192 | return i18nc("Exposure compensation given as integral with fraction, in exposure value (EV)" , |
| 193 | "%1 %2/%3 EV" , locale.toString(integral), locale.toString(abs(nominator)), locale.toString(denominator)); |
| 194 | } |
| 195 | return i18nc("Exposure compensation given as rational, in exposure value (EV)" , |
| 196 | "%1/%2 EV" , locale.toString(nominator), locale.toString(denominator)); |
| 197 | } |
| 198 | |
| 199 | QString FormatStrings::formatAspectRatio(const QVariant& value) |
| 200 | { |
| 201 | return i18nc("Aspect ratio, normalized to one" , "%1:1" , QLocale().toString(round(value.toDouble() * 100) / 100)); |
| 202 | } |
| 203 | |
| 204 | QString FormatStrings::formatAsFNumber(const QVariant& value) |
| 205 | { |
| 206 | return i18nc("F number for photographs" , "f/%1" , QLocale().toString(value.toDouble(), 'g', 2)); |
| 207 | } |
| 208 | |