QCodeEditor: Update to current cpeditor/QCodeEditor fork, commit ed1196a
[smuview.git] / src / util.cpp
blobabf768326c89e84f6595c879eab74b908317f31b
1 /*
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/>.
21 #include <algorithm>
22 #include <cassert>
23 #include <limits>
24 #include <math.h>
25 #include <sstream>
26 #include <vector>
28 #include <libsigrokcxx/libsigrokcxx.hpp>
30 #include <QDateTime>
31 #include <QDebug>
32 #include <QTextStream>
33 #include <QUuid>
35 #include "extdef.h"
36 #include "util.hpp"
38 using std::fixed;
39 using std::max;
40 using std::ostringstream;
41 using std::setfill;
42 using std::setprecision;
43 using std::showpos;
44 using std::string;
45 using std::vector;
47 namespace sv {
48 namespace util {
50 static QTextStream &operator<<(QTextStream &stream, SIPrefix prefix)
52 switch (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 &timestamp)
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();
109 ostringstream ss;
110 ss << fixed;
112 if ((stream.numberFlags() & QTextStream::ForceSign) != 0)
113 ss << showpos;
115 if (0 == precision)
116 ss << setprecision(1) << round(timestamp);
117 else
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;
147 return prefix;
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)
184 SIPrefix si_prefix;
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;
191 else {
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);
197 exp -= 3;
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 &timestamp, 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;
223 else {
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);
229 exp -= 3;
234 assert(prefix >= SIPrefix::yocto);
235 assert(prefix <= SIPrefix::yotta);
237 const Timestamp multiplier = pow(Timestamp(10), -exponent(prefix));
239 QString str;
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;
246 return str;
249 QString format_time_si_adjusted(const Timestamp &timestamp, 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 &timestamp, signed precision,
269 bool sign)
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>();
277 QString str;
278 QTextStream ts(&str);
280 if (timestamp < 0)
281 ts << "-";
282 else if (sign)
283 ts << "+";
285 bool use_padding = false;
287 // DD
288 if (days) {
289 ts << days.str().c_str() << ":";
290 use_padding = true;
293 // HH
294 if (hours || days) {
295 ts << pad_number(hours, use_padding ? 2 : 0) << ":";
296 use_padding = true;
299 // MM
300 ts << pad_number(minutes, use_padding ? 2 : 0);
302 ts << ":";
304 // SS
305 ts << pad_number(seconds, 2);
307 if (precision) {
308 ts << ".";
310 const Timestamp fraction = fabs(timestamp) - whole_seconds;
312 ostringstream ss;
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
319 ts << fs.at(1 + i);
321 if ((i > 0) && (i % 3 == 0) && (i != precision))
322 ts << " ";
326 return str;
329 QString format_time_date(double timestamp)
331 QDateTime date;
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();
340 #else
341 return uuid.toString().replace("{", "").replace("}", "").toStdString();
342 #endif
345 vector<string> split_string(string text, const string &separator)
347 vector<string> result;
348 size_t pos;
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);
356 return result;
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)
367 if (number == 0)
368 return 0;
369 int abs_number = abs(number);
370 int digits = 1;
371 while (abs_number >= 10) {
372 abs_number /= 10;
373 digits++;
375 return digits;
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);
389 if (frac_part == 0)
390 return 0;
392 std::stringstream stream;
393 stream << frac_part;
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);
409 return 0;
412 * Old implementation, didn't worked for e.g. `0.111`
413 double frac_part = fmod(step, 1.0);
414 if (frac_part == 0)
415 return 0;
416 int decimal = (int)floor(1/frac_part) - 1;
417 return util::count_int_digits(decimal);
421 int get_sr_digits(double step)
423 if (step == 0)
424 return 0;
426 int count_frac = util::count_decimal_places(fmod(step, 1.0));
427 if (count_frac > 0)
428 return count_frac;
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();
435 int int_count = 0;
436 size_t int_str_pos = int_str.length();
437 while (int_str_pos > 0) {
438 int_str_pos--;
439 if (int_str[int_str_pos] != '0')
440 break;
441 int_count++;
444 return -int_count;
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) {
457 switch (state) {
458 case State::UnquotedField:
459 switch (chr) {
460 case ',':
461 // end of field
462 fields.push_back("");
463 index++;
464 break;
465 case '"':
466 state = State::QuotedField;
467 break;
468 default:
469 fields[index].push_back(chr);
470 break;
472 break;
473 case State::QuotedField:
474 switch (chr) {
475 case '"':
476 state = State::QuotedQuote;
477 break;
478 default:
479 fields[index].push_back(chr);
480 break;
482 break;
483 case State::QuotedQuote:
484 switch (chr) {
485 case ',':
486 // , after closing quote
487 fields.push_back("");
488 index++;
489 state = State::UnquotedField;
490 break;
491 case '"':
492 // "" -> "
493 fields[index].push_back('"');
494 state = State::QuotedField;
495 break;
496 default:
497 // end of quote
498 state = State::UnquotedField;
499 break;
501 break;
505 return fields;
508 } // namespace util
509 } // namespace sv