2 * This file is part of the SmuView project.
4 * Copyright (C) 2012 Joel Holdsworth <joel@airwebreathe.org.uk>
5 * Copyright (C) 2017-2022 Frank Stettner <frank-stettner@gmx.net>
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
28 #include <libsigrokcxx/libsigrokcxx.hpp>
32 #include <QTextStream>
40 using std::ostringstream
;
42 using std::setprecision
;
50 static QTextStream
&operator<<(QTextStream
&stream
, SIPrefix prefix
)
53 case SIPrefix::yocto
: return stream
<< 'y';
54 case SIPrefix::zepto
: return stream
<< 'z';
55 case SIPrefix::atto
: return stream
<< 'a';
56 case SIPrefix::femto
: return stream
<< 'f';
57 case SIPrefix::pico
: return stream
<< 'p';
58 case SIPrefix::nano
: return stream
<< 'n';
59 case SIPrefix::micro
: return stream
<< QChar(0x03BC);
60 case SIPrefix::milli
: return stream
<< 'm';
61 case SIPrefix::kilo
: return stream
<< 'k';
62 case SIPrefix::mega
: return stream
<< 'M';
63 case SIPrefix::giga
: return stream
<< 'G';
64 case SIPrefix::tera
: return stream
<< 'T';
65 case SIPrefix::peta
: return stream
<< 'P';
66 case SIPrefix::exa
: return stream
<< 'E';
67 case SIPrefix::zetta
: return stream
<< 'Z';
68 case SIPrefix::yotta
: return stream
<< 'Y';
70 default: return stream
;
74 int exponent(SIPrefix prefix
)
76 return 3 * (static_cast<int>(prefix
) - static_cast<int>(SIPrefix::none
));
79 static SIPrefix
successor(SIPrefix prefix
)
81 assert(prefix
!= SIPrefix::yotta
);
82 return static_cast<SIPrefix
>(static_cast<int>(prefix
) + 1);
85 static int prefix_from_si_prefix(SIPrefix prefix
)
87 return static_cast<int>(prefix
) - static_cast<int>(SIPrefix::none
);
90 static SIPrefix
si_prefix_from_prefix(int prefix
)
92 return static_cast<SIPrefix
>(static_cast<int>(SIPrefix::none
) + prefix
);
95 // Insert the timestamp value into the stream in fixed-point notation
96 // (and honor the precision)
97 static QTextStream
&operator<<(QTextStream
&stream
, const Timestamp
×tamp
)
99 // The multiprecision types already have a function and a stream insertion
100 // operator to convert them to a string, however these functions abuse a
101 // precision value of zero to print all available decimal places instead of
102 // none, and the boost authors refuse to fix this because they don't want
103 // to break buggy code that relies on this bug.
104 // (https://svn.boost.org/trac/boost/ticket/10103)
105 // Therefore we have to work around the case where precision is zero.
107 int precision
= stream
.realNumberPrecision();
112 if ((stream
.numberFlags() & QTextStream::ForceSign
) != 0)
116 ss
<< setprecision(1) << round(timestamp
);
118 ss
<< setprecision(precision
) << timestamp
;
120 string
str(ss
.str());
121 if (0 == precision
) {
122 // remove the separator and the unwanted decimal place
123 str
.resize(str
.size() - 2);
126 return stream
<< QString::fromStdString(str
);
129 int prefix_from_value(const double value
, const int sr_digits
)
131 double logval
= log10(fabs(value
));
132 int prefix
= static_cast<int>((logval
/ 3) - static_cast<int>(logval
< 1));
134 if (value
== 0 || value
== NAN
||
135 value
== std::numeric_limits
<double>::infinity() ||
136 value
>= std::numeric_limits
<double>::max() ||
137 value
<= std::numeric_limits
<double>::lowest()) {
138 prefix
= prefix_from_si_prefix(SIPrefix::none
);
140 else if (prefix
< prefix_from_si_prefix(SIPrefix::yocto
))
141 prefix
= prefix_from_si_prefix(SIPrefix::yocto
);
142 else if (prefix
> prefix_from_si_prefix(SIPrefix::yotta
))
143 prefix
= prefix_from_si_prefix(SIPrefix::yotta
);
144 else if (3 * prefix
< -sr_digits
)
145 prefix
= (-sr_digits
+ 2 * static_cast<int>(sr_digits
< 0)) / 3;
150 int decimal_places_from_prefix(const int prefix
, const int sr_digits
)
152 int decimal_places
= sr_digits
+ (3 * prefix
);
153 decimal_places
= decimal_places
< 0 ? 0 : decimal_places
;
155 return decimal_places
;
158 void format_value_si(
159 const double value
, const int total_digits
, const int sr_digits
,
160 QString
&value_str
, QString
&si_prefix_str
, const bool use_locale
)
162 int prefix
= prefix_from_value(value
, sr_digits
);
163 SIPrefix si_prefix
= si_prefix_from_prefix(prefix
);
164 assert(si_prefix
>= SIPrefix::yocto
);
165 assert(si_prefix
<= SIPrefix::yotta
);
167 int decimal_places
= decimal_places_from_prefix(prefix
, sr_digits
);
169 double new_value
= value
* pow(10, -3 * prefix
);
171 // Check if, use current locale (%L) for formating.
172 QString format_string
= use_locale
? "%L1" : "%1";
173 value_str
= QString(format_string
)
174 .arg(new_value
, total_digits
, 'f', decimal_places
, QChar(' '));
176 QTextStream
si_prefix_stream(&si_prefix_str
);
177 si_prefix_stream
<< si_prefix
;
180 void format_value_si_autoscale(
181 const double value
, const int total_digits
, const int decimal_places
,
182 QString
&value_str
, QString
&si_prefix_str
, const bool use_locale
)
185 if (value
== 0 || value
== NAN
||
186 value
== std::numeric_limits
<double>::infinity() ||
187 value
>= std::numeric_limits
<double>::max() ||
188 value
<= std::numeric_limits
<double>::lowest()) {
189 si_prefix
= SIPrefix::none
;
192 int exp
= exponent(SIPrefix::yotta
);
193 si_prefix
= SIPrefix::yocto
;
194 while ((fabs(value
) * pow(10, exp
)) > 999 &&
195 si_prefix
< SIPrefix::yotta
) {
196 si_prefix
= successor(si_prefix
);
200 assert(si_prefix
>= SIPrefix::yocto
);
201 assert(si_prefix
<= SIPrefix::yotta
);
203 const double multiplier
= pow(10, -exponent(si_prefix
));
205 // Check if, use current locale (%L) for formating.
206 QString format_string
= use_locale
? "%L1" : "%1";
207 value_str
= QString(format_string
).
208 arg(value
* multiplier
, total_digits
, 'f', decimal_places
, QChar(' '));
210 QTextStream
si_prefix_stream(&si_prefix_str
);
211 si_prefix_stream
<< si_prefix
;
214 QString
format_time_si(const Timestamp
×tamp
, SIPrefix prefix
,
215 unsigned int precision
, const QString
&unit
, bool sign
)
217 if (prefix
== SIPrefix::unspecified
) {
218 // No prefix given, calculate it
220 if (timestamp
.is_zero()) {
221 prefix
= SIPrefix::none
;
224 int exp
= exponent(SIPrefix::yotta
);
225 prefix
= SIPrefix::yocto
;
226 while ((fabs(timestamp
) * pow(Timestamp(10), exp
)) > 999 &&
227 prefix
< SIPrefix::yotta
) {
228 prefix
= successor(prefix
);
234 assert(prefix
>= SIPrefix::yocto
);
235 assert(prefix
<= SIPrefix::yotta
);
237 const Timestamp multiplier
= pow(Timestamp(10), -exponent(prefix
));
240 QTextStream
ts(&str
);
241 if (sign
&& !timestamp
.is_zero())
242 ts
.setNumberFlags(ts
.numberFlags() | QTextStream::ForceSign
);
243 ts
<< qSetRealNumberPrecision((int)precision
) << (timestamp
* multiplier
)
244 << ' ' << prefix
<< unit
;
249 QString
format_time_si_adjusted(const Timestamp
×tamp
, SIPrefix prefix
,
250 unsigned precision
, const QString
&unit
, bool sign
)
252 // The precision is always given without taking the prefix into account
253 // so we need to deduct the number of decimals the prefix might imply
254 const int prefix_order
= -exponent(prefix
);
256 const unsigned int relative_prec
= (prefix
>= SIPrefix::none
) ?
257 precision
: max((int)(precision
- prefix_order
), 0);
259 return format_time_si(timestamp
, prefix
, relative_prec
, unit
, sign
);
262 // Helper for 'format_time_minutes()'.
263 static QString
pad_number(unsigned int number
, int length
)
265 return QString("%1").arg(number
, length
, 10, QChar('0'));
268 QString
format_time_minutes(const Timestamp
×tamp
, signed precision
,
271 const Timestamp whole_seconds
= floor(abs(timestamp
));
272 const Timestamp days
= floor(whole_seconds
/ (60 * 60 * 24));
273 const unsigned int hours
= fmod(whole_seconds
/ (60 * 60), 24).convert_to
<uint
>();
274 const unsigned int minutes
= fmod(whole_seconds
/ 60, 60).convert_to
<uint
>();
275 const unsigned int seconds
= fmod(whole_seconds
, 60).convert_to
<uint
>();
278 QTextStream
ts(&str
);
285 bool use_padding
= false;
289 ts
<< days
.str().c_str() << ":";
295 ts
<< pad_number(hours
, use_padding
? 2 : 0) << ":";
300 ts
<< pad_number(minutes
, use_padding
? 2 : 0);
305 ts
<< pad_number(seconds
, 2);
310 const Timestamp fraction
= fabs(timestamp
) - whole_seconds
;
313 ss
<< fixed
<< setprecision(precision
) << setfill('0') << fraction
;
314 string fs
= ss
.str();
316 // Copy all digits, inserting spaces as unit separators
317 for (int i
= 1; i
<= precision
; i
++) {
318 // Start at index 2 to skip the "0." at the beginning
321 if ((i
> 0) && (i
% 3 == 0) && (i
!= precision
))
329 QString
format_time_date(double timestamp
)
332 date
.setMSecsSinceEpoch(static_cast<qint64
>(timestamp
* 1000));
333 return date
.toString("yyyy.MM.dd hh:mm:ss.zzz");
336 string
format_uuid(QUuid uuid
)
338 #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
339 return uuid
.toString(QUuid::WithoutBraces
).toStdString();
341 return uuid
.toString().replace("{", "").replace("}", "").toStdString();
345 vector
<string
> split_string(string text
, const string
&separator
)
347 vector
<string
> result
;
350 while ((pos
= text
.find(separator
)) != std::string::npos
) {
351 result
.push_back(text
.substr(0, pos
));
352 text
= text
.substr(pos
+ separator
.length());
354 result
.push_back(text
);
359 bool starts_with(const string
&str
, const string
&start_str
)
361 return start_str
.length() <= str
.length() &&
362 std::equal(start_str
.begin(), start_str
.end(), str
.begin());
365 int count_int_digits(int number
)
369 int abs_number
= abs(number
);
371 while (abs_number
>= 10) {
378 int count_double_digits(double max_value
, double step
)
380 int count_int
= util::count_int_digits((int)floor(max_value
));
381 int count_frac
= util::count_decimal_places(fmod(max_value
, 1.0));
382 int count_step
= util::count_decimal_places(step
);
383 return count_int
+ (count_frac
> count_step
? count_frac
: count_step
);
386 int count_decimal_places(double step
)
388 double frac_part
= fmod(step
, 1.0);
392 std::stringstream stream
;
394 std::string frac_str
= stream
.str();
396 // Check for exponential notation
397 size_t e_pos
= frac_str
.find('e');
398 if (e_pos
!= std::string::npos
) {
399 std::string exponent_str
= frac_str
.substr(e_pos
+ 1);
400 return -(std::stoi(exponent_str
));
403 // Check for the decimal point
404 size_t point_pos
= frac_str
.find('.');
405 if (point_pos
!= std::string::npos
) {
406 return static_cast<int>(frac_str
.length() - point_pos
- 1);
412 * Old implementation, didn't worked for e.g. `0.111`
413 double frac_part = fmod(step, 1.0);
416 int decimal = (int)floor(1/frac_part) - 1;
417 return util::count_int_digits(decimal);
421 int get_sr_digits(double step
)
426 int count_frac
= util::count_decimal_places(fmod(step
, 1.0));
430 // Count the zeros at the end of the integer part
431 std::stringstream stream
;
432 stream
<< static_cast<int>(floor(step
));
433 std::string int_str
= stream
.str();
436 size_t int_str_pos
= int_str
.length();
437 while (int_str_pos
> 0) {
439 if (int_str
[int_str_pos
] != '0')
448 * Based on https://stackoverflow.com/a/30338543
450 vector
<string
> parse_csv_line(const string
&line
)
452 enum State
{ UnquotedField
, QuotedField
, QuotedQuote
} state
= UnquotedField
;
453 std::vector
<std::string
> fields
{""};
455 size_t index
= 0; // index of the current field
456 for (char chr
: line
) {
458 case State::UnquotedField
:
462 fields
.push_back("");
466 state
= State::QuotedField
;
469 fields
[index
].push_back(chr
);
473 case State::QuotedField
:
476 state
= State::QuotedQuote
;
479 fields
[index
].push_back(chr
);
483 case State::QuotedQuote
:
486 // , after closing quote
487 fields
.push_back("");
489 state
= State::UnquotedField
;
493 fields
[index
].push_back('"');
494 state
= State::QuotedField
;
498 state
= State::UnquotedField
;