1 /* 2 * LibrePCB - Professional EDA for everyone! 3 * Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors. 4 * https://librepcb.org/ 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 #ifndef LIBREPCB_TOOLBOX_H 21 #define LIBREPCB_TOOLBOX_H 22 23 /******************************************************************************* 24 * Includes 25 ******************************************************************************/ 26 #include "units/all_length_units.h" 27 28 #include <type_traits> 29 30 #include <QtCore> 31 #include <QtWidgets> 32 33 #include <algorithm> 34 35 /******************************************************************************* 36 * Namespace / Forward Declarations 37 ******************************************************************************/ 38 namespace librepcb { 39 40 /******************************************************************************* 41 * Class Toolbox 42 ******************************************************************************/ 43 44 /** 45 * @brief The Toolbox class provides some useful general purpose methods 46 */ 47 class Toolbox final { 48 Q_DECLARE_TR_FUNCTIONS(Toolbox) 49 50 public: 51 // Constructors / Destructor 52 Toolbox() = delete; 53 Toolbox(const Toolbox& other) = delete; 54 ~Toolbox() = delete; 55 56 // Operator Overloadings 57 Toolbox& operator=(const Toolbox& rhs) = delete; 58 59 // Static Methods 60 61 /** 62 * @brief Helper method to convert a QList<T> to a QSet<T> 63 * 64 * Until Qt 5.13, QList::toSet() was the way to do this conversion. But since 65 * Qt 5.14 this is deprecated, and a range constructor was added to QSet 66 * instead. This wrapper chooses the proper method depending on the Qt 67 * version to avoid raising any deprecation warnings. 68 * 69 * @param list The QList to be converted. 70 * 71 * @return The created QSet. 72 */ 73 template <typename T> toSet(const QList<T> & list)74 static QSet<T> toSet(const QList<T>& list) noexcept { 75 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) 76 return QSet<T>(list.begin(), list.end()); 77 #else 78 return list.toSet(); 79 #endif 80 } 81 82 template <typename T> sortedQSet(const QSet<T> & set)83 static QList<T> sortedQSet(const QSet<T>& set) noexcept { 84 QList<T> list = set.values(); 85 std::sort(list.begin(), list.end()); 86 return list; 87 } 88 89 template <typename T> sorted(const T & container)90 static T sorted(const T& container) noexcept { 91 T copy(container); 92 std::sort(copy.begin(), copy.end()); 93 return copy; 94 } 95 boundingRectFromRadius(qreal radius)96 static QRectF boundingRectFromRadius(qreal radius) noexcept { 97 return QRectF(-radius, -radius, 2 * radius, 2 * radius); 98 } 99 boundingRectFromRadius(qreal rx,qreal ry)100 static QRectF boundingRectFromRadius(qreal rx, qreal ry) noexcept { 101 return QRectF(-rx, -ry, 2 * rx, 2 * ry); 102 } 103 adjustedBoundingRect(const QRectF & rect,qreal offset)104 static QRectF adjustedBoundingRect(const QRectF& rect, 105 qreal offset) noexcept { 106 return rect.adjusted(-offset, -offset, offset, offset); 107 } 108 109 static QPainterPath shapeFromPath( 110 const QPainterPath& path, const QPen& pen, const QBrush& brush, 111 const UnsignedLength& minWidth = UnsignedLength(0)) noexcept; 112 113 static Length arcRadius(const Point& p1, const Point& p2, 114 const Angle& a) noexcept; 115 static Point arcCenter(const Point& p1, const Point& p2, 116 const Angle& a) noexcept; 117 118 /** 119 * @brief Calculate the point on a given line which is nearest to a given 120 * point 121 * 122 * @param p An arbitrary point 123 * @param l1 Start point of the line 124 * @param l2 End point of the line 125 * 126 * @return Nearest point on the given line (either l1, l2, or a point between 127 * them) 128 * 129 * @warning This method works with floating point numbers and thus the result 130 * may not be perfectly precise. 131 */ 132 static Point nearestPointOnLine(const Point& p, const Point& l1, 133 const Point& l2) noexcept; 134 135 /** 136 * @brief Calculate the shortest distance between a given point and a given 137 * line 138 * 139 * @param p An arbitrary point 140 * @param l1 Start point of the line 141 * @param l2 End point of the line 142 * @param nearest If not `nullptr`, the nearest point is returned here 143 * 144 * @return Shortest distance between the given point and the given line (>=0) 145 */ 146 static UnsignedLength shortestDistanceBetweenPointAndLine( 147 const Point& p, const Point& l1, const Point& l2, 148 Point* nearest = nullptr) noexcept; 149 150 /** 151 * @brief Copy a string while incrementing its contained number 152 * 153 * - If the string contains one or more numbers, the last one gets incremented 154 * - If it does not contain a number, a "1" is appended instead 155 * 156 * This way, the returned number is guaranteed to be different from the input 157 * string. That's useful for example to generate unique, incrementing pin 158 * numbers like "X1", "X2", "X3" etc. 159 * 160 * @param string The input string 161 * @return A new string with the incremented number 162 */ 163 static QString incrementNumberInString(QString string) noexcept; 164 165 /** 166 * @brief Expand ranges like "1..5" in a string to all its values 167 * 168 * A range is either defined by two integers with ".." in between, or two 169 * ASCII letters with ".." in between. If multiple ranges are contained, all 170 * combinations of them will be created. 171 * 172 * For example the string "X1..10_A..C" expands to the list ["X1_A", "X1_B", 173 * "X1_C", ..., "X10_C"]. 174 * 175 * @note Minus ('-') and plus ('+') characters are not interpreted as the 176 * sign of a number because in EDA tools they often are considered as 177 * strings, not as number signs (e.g. the inputs of an OpAmp). 178 * 179 * @param string The input string (may or may not contain ranges). 180 * @return A list with expanded ranges in all combinations. If the input 181 * string does not contain ranges, a list with one element (equal to 182 * the input) is returned. 183 */ 184 static QStringList expandRangesInString(const QString& string) noexcept; 185 186 /** 187 * @brief Clean a user input string 188 * 189 * @param input The string typed by the user 190 * @param removeRegex Regex for all patterns to remove from the string 191 * @param trim If true, leading and trailing spaces are removed 192 * @param toLower If true, all characters are converted to lowercase 193 * @param toUpper If true, all characters are converted to uppercase 194 * @param spaceReplacement All spaces are replaced by this string 195 * @param maxLength If >= 0, the string is truncated to this length 196 * 197 * @return The cleaned string (may be empty) 198 */ 199 static QString cleanUserInputString(const QString& input, 200 const QRegularExpression& removeRegex, 201 bool trim = true, bool toLower = false, 202 bool toUpper = false, 203 const QString& spaceReplacement = " ", 204 int maxLength = -1) noexcept; 205 206 /** 207 * @brief Pretty print the name of a QLocale 208 * 209 * To show locale names in the UI. Examples: 210 * 211 * - "en_US" -> "American English (United States)" 212 * - "de" -> "Deutsch" 213 * - "eo" -> "Esperanto" 214 * 215 * @param code The code of the locale to print (e.g. "en_US"). 216 * 217 * @return String with the pretty printed locale name. 218 */ 219 static QString prettyPrintLocale(const QString& code) noexcept; 220 221 /** 222 * @brief Convert a float or double to a localized string 223 * 224 * Same as QLocale::toString<float/double>(), but with omitted trailing zeros 225 * and without group separators. 226 */ 227 template <typename T> floatToString(T value,int decimals,const QLocale & locale)228 static QString floatToString(T value, int decimals, 229 const QLocale& locale) noexcept { 230 QString s = locale.toString(value, 'f', decimals); 231 for (int i = 1; (i < decimals) && s.endsWith(locale.zeroDigit()); ++i) { 232 s.chop(1); 233 } 234 if (qAbs(value) >= 1000) { 235 s.remove(locale.groupSeparator()); 236 } 237 return s; 238 } 239 240 /** 241 * @brief Convert a fixed point decimal number from an integer to a QString 242 * 243 * @param value Value to convert 244 * @param pointPos Number of fixed point decimal positions 245 */ 246 template <typename T> decimalFixedPointToString(T value,qint32 pointPos)247 static QString decimalFixedPointToString(T value, qint32 pointPos) noexcept { 248 using UnsignedT = typename std::make_unsigned<T>::type; 249 250 if (value == 0) { 251 // special case 252 return "0.0"; 253 } 254 255 UnsignedT valueAbs; 256 if (value < 0) { 257 valueAbs = -static_cast<UnsignedT>(value); 258 } else { 259 valueAbs = static_cast<UnsignedT>(value); 260 } 261 262 QString str = QString::number(valueAbs); 263 if (str.length() > pointPos) { 264 // pointPos must be > 0 for this to work correctly 265 str.insert(str.length() - pointPos, '.'); 266 } else { 267 for (qint32 i = pointPos - str.length(); i != 0; i--) str.insert(0, '0'); 268 str.insert(0, "0."); 269 } 270 271 while (str.endsWith('0') && !str.endsWith(".0")) str.chop(1); 272 273 if (value < 0) str.insert(0, '-'); 274 275 return str; 276 } 277 278 /** 279 * @brief Convert a fixed point decimal number from a QString to an integer. 280 * 281 * @param str A QString that represents the number 282 * @param pointPos Number of decimal positions. If the number has more 283 * decimal digits, this function will throw 284 */ 285 template <typename T> decimalFixedPointFromString(const QString & str,qint32 pointPos)286 static T decimalFixedPointFromString(const QString& str, qint32 pointPos) { 287 using UnsignedT = typename std::make_unsigned<T>::type; 288 289 const T min = std::numeric_limits<T>::min(); 290 const T max = std::numeric_limits<T>::max(); 291 const UnsignedT max_u = std::numeric_limits<UnsignedT>::max(); 292 293 enum class State { 294 INVALID, 295 START, 296 AFTER_SIGN, 297 LONELY_DOT, 298 INT_PART, 299 FRAC_PART, 300 EXP, 301 EXP_AFTER_SIGN, 302 EXP_DIGITS, 303 }; 304 State state = State::START; 305 UnsignedT valueAbs = 0; 306 bool sign = false; 307 qint32 expOffset = pointPos; 308 309 const quint32 maxExp = std::numeric_limits<quint32>::max(); 310 quint32 exp = 0; 311 bool expSign = false; 312 313 for (QChar c : str) { 314 if (state == State::INVALID) { 315 // break the loop, not the switch 316 break; 317 } 318 switch (state) { 319 case State::INVALID: 320 // already checked, but needed to avoid compiler warnings 321 break; 322 323 case State::START: 324 if (c == '-') { 325 sign = true; 326 state = State::AFTER_SIGN; 327 } else if (c == '+') { 328 state = State::AFTER_SIGN; 329 } else if (c == '.') { 330 state = State::LONELY_DOT; 331 } else if (c.isDigit()) { 332 valueAbs = static_cast<UnsignedT>(c.digitValue()); 333 state = State::INT_PART; 334 } else { 335 state = State::INVALID; 336 } 337 break; 338 339 case State::AFTER_SIGN: 340 if (c == '.') { 341 state = State::LONELY_DOT; 342 } else if (c.isDigit()) { 343 valueAbs = static_cast<UnsignedT>(c.digitValue()); 344 state = State::INT_PART; 345 } else { 346 state = State::INVALID; 347 } 348 break; 349 350 case State::LONELY_DOT: 351 if (c.isDigit()) { 352 valueAbs = static_cast<UnsignedT>(c.digitValue()); 353 expOffset -= 1; 354 state = State::FRAC_PART; 355 } else { 356 state = State::INVALID; 357 } 358 break; 359 360 case State::INT_PART: 361 if (c == '.') { 362 state = State::FRAC_PART; 363 } else if (c == 'e' || c == 'E') { 364 state = State::EXP; 365 } else if (c.isDigit()) { 366 UnsignedT digit = static_cast<UnsignedT>(c.digitValue()); 367 if (valueAbs > (max_u / 10)) { 368 // Would overflow 369 state = State::INVALID; 370 break; 371 } 372 valueAbs *= 10; 373 if (valueAbs > (max_u - digit)) { 374 // Would overflow 375 state = State::INVALID; 376 break; 377 } 378 valueAbs += digit; 379 } else { 380 state = State::INVALID; 381 } 382 break; 383 384 case State::FRAC_PART: 385 if (c == 'e' || c == 'E') { 386 state = State::EXP; 387 } else if (c.isDigit()) { 388 UnsignedT digit = static_cast<UnsignedT>(c.digitValue()); 389 if (valueAbs > (max_u / 10)) { 390 // Would overflow 391 state = State::INVALID; 392 break; 393 } 394 valueAbs *= 10; 395 if (valueAbs > (max_u - digit)) { 396 // Would overflow 397 state = State::INVALID; 398 break; 399 } 400 valueAbs += digit; 401 expOffset -= 1; 402 } else { 403 state = State::INVALID; 404 } 405 break; 406 407 case State::EXP: 408 if (c == '-') { 409 expSign = true; 410 state = State::EXP_AFTER_SIGN; 411 } else if (c == '+') { 412 state = State::EXP_AFTER_SIGN; 413 } else if (c.isDigit()) { 414 exp = static_cast<quint32>(c.digitValue()); 415 state = State::EXP_DIGITS; 416 } else { 417 state = State::INVALID; 418 } 419 break; 420 421 case State::EXP_AFTER_SIGN: 422 if (c.isDigit()) { 423 exp = static_cast<quint32>(c.digitValue()); 424 state = State::EXP_DIGITS; 425 } else { 426 state = State::INVALID; 427 } 428 break; 429 430 case State::EXP_DIGITS: 431 if (c.isDigit()) { 432 quint32 digit = static_cast<quint32>(c.digitValue()); 433 if (exp > (maxExp / 10)) { 434 // Would overflow 435 state = State::INVALID; 436 break; 437 } 438 exp *= 10; 439 if (exp > (maxExp - digit)) { 440 // Would overflow 441 state = State::INVALID; 442 break; 443 } 444 exp += digit; 445 } else { 446 state = State::INVALID; 447 } 448 } 449 } 450 451 bool ok = true; 452 switch (state) { 453 case State::INVALID: 454 case State::START: 455 case State::AFTER_SIGN: 456 case State::LONELY_DOT: 457 case State::EXP: 458 case State::EXP_AFTER_SIGN: 459 ok = false; 460 break; 461 462 case State::INT_PART: 463 case State::FRAC_PART: 464 case State::EXP_DIGITS: 465 break; 466 } 467 468 if (ok) { 469 quint32 expOffsetAbs; 470 if (expOffset < 0) { 471 expOffsetAbs = -static_cast<quint32>(expOffset); 472 } else { 473 expOffsetAbs = static_cast<quint32>(expOffset); 474 } 475 476 if (expSign == (expOffset < 0)) { 477 if (exp > (maxExp - expOffsetAbs)) { 478 // would overflow 479 ok = false; 480 } else { 481 exp += expOffsetAbs; 482 } 483 } else { 484 if (exp < expOffsetAbs) { 485 // would overflow 486 ok = false; 487 } else { 488 exp -= expOffsetAbs; 489 } 490 } 491 } 492 493 T result = 0; 494 if (ok) { 495 // No need to apply exponent or sign if valueAbs is zero 496 if (valueAbs != 0) { 497 if (expSign) { 498 for (quint32 i = 0; i < exp; i++) { 499 if ((valueAbs % 10) != 0) { 500 // more decimal digits than allowed 501 ok = false; 502 break; 503 } 504 valueAbs /= 10; 505 } 506 } else { 507 for (quint32 i = 0; i < exp; i++) { 508 if (valueAbs > (max_u / 10)) { 509 // would overflow 510 ok = false; 511 break; 512 } 513 valueAbs *= 10; 514 } 515 } 516 if (ok) { 517 if (sign) { 518 if (valueAbs > static_cast<UnsignedT>(min)) { 519 ok = false; 520 } else { 521 result = static_cast<T>(-valueAbs); 522 } 523 } else { 524 if (valueAbs > static_cast<UnsignedT>(max)) { 525 ok = false; 526 } else { 527 result = static_cast<T>(valueAbs); 528 } 529 } 530 } 531 } 532 } 533 534 if (!ok) { 535 throw RuntimeError( 536 __FILE__, __LINE__, 537 tr("Invalid fixed point number string: \"%1\"").arg(str)); 538 } 539 return result; 540 } 541 542 private: 543 /** 544 * @brief Internal helper function for #expandRangesInString(const QString&) 545 */ 546 static QStringList expandRangesInString( 547 const QString& input, 548 const QVector<std::tuple<int, int, QStringList>>& replacements) noexcept; 549 }; 550 551 /******************************************************************************* 552 * End of File 553 ******************************************************************************/ 554 555 } // namespace librepcb 556 557 #endif // LIBREPCB_TOOLBOX_H 558