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