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