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 /*******************************************************************************
21 * Includes
22 ******************************************************************************/
23 #include "toolbox.h"
24
25 #include <QtCore>
26
27 /*******************************************************************************
28 * Namespace
29 ******************************************************************************/
30 namespace librepcb {
31
32 /*******************************************************************************
33 * Static Methods
34 ******************************************************************************/
35
shapeFromPath(const QPainterPath & path,const QPen & pen,const QBrush & brush,const UnsignedLength & minWidth)36 QPainterPath Toolbox::shapeFromPath(const QPainterPath& path, const QPen& pen,
37 const QBrush& brush,
38 const UnsignedLength& minWidth) noexcept {
39 // http://code.qt.io/cgit/qt/qtbase.git/tree/src/widgets/graphicsview/qgraphicsitem.cpp
40 // Function: qt_graphicsItem_shapeFromPath()
41
42 if ((path.isEmpty()) || (pen.style() == Qt::NoPen) ||
43 (pen.brush().style() == Qt::NoBrush)) {
44 return path;
45 } else {
46 QPainterPathStroker ps;
47 ps.setCapStyle(pen.capStyle());
48 ps.setWidth(qMax(qMax(pen.widthF(), qreal(0.00000001)), minWidth->toPx()));
49 ps.setJoinStyle(pen.joinStyle());
50 ps.setMiterLimit(pen.miterLimit());
51 QPainterPath p = ps.createStroke(path);
52 if (brush.style() != Qt::NoBrush) {
53 p.addPath(path);
54 }
55 return p;
56 }
57 }
58
arcRadius(const Point & p1,const Point & p2,const Angle & a)59 Length Toolbox::arcRadius(const Point& p1, const Point& p2,
60 const Angle& a) noexcept {
61 if (a == 0) {
62 return Length(0);
63 } else {
64 qreal x1 = p1.getX().toMm();
65 qreal y1 = p1.getY().toMm();
66 qreal x2 = p2.getX().toMm();
67 qreal y2 = p2.getY().toMm();
68 qreal angle = a.mappedTo180deg().toRad();
69 qreal d = qSqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
70 qreal r = d / (2 * qSin(angle / 2));
71 return Length::fromMm(r);
72 }
73 }
74
arcCenter(const Point & p1,const Point & p2,const Angle & a)75 Point Toolbox::arcCenter(const Point& p1, const Point& p2,
76 const Angle& a) noexcept {
77 if (a == 0) {
78 // there is no arc center...just return the middle of start- and endpoint
79 return (p1 + p2) / 2;
80 } else {
81 // http://math.stackexchange.com/questions/27535/how-to-find-center-of-an-arc-given-start-point-end-point-radius-and-arc-direc
82 qreal x0 = p1.getX().toMm();
83 qreal y0 = p1.getY().toMm();
84 qreal x1 = p2.getX().toMm();
85 qreal y1 = p2.getY().toMm();
86 qreal angle = a.mappedTo180deg().toRad();
87 qreal angleSgn = (angle >= 0) ? 1 : -1;
88 qreal d = qSqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0));
89 qreal r = d / (2 * qSin(angle / 2));
90 qreal h = qSqrt(r * r - d * d / 4);
91 qreal u = (x1 - x0) / d;
92 qreal v = (y1 - y0) / d;
93 qreal a = ((x0 + x1) / 2) - h * v * angleSgn;
94 qreal b = ((y0 + y1) / 2) + h * u * angleSgn;
95 return Point::fromMm(a, b);
96 }
97 }
98
nearestPointOnLine(const Point & p,const Point & l1,const Point & l2)99 Point Toolbox::nearestPointOnLine(const Point& p, const Point& l1,
100 const Point& l2) noexcept {
101 Point a = l2 - l1;
102 Point b = p - l1;
103 Point c = p - l2;
104 qreal d = ((b.getX().toMm() * a.getX().toMm()) +
105 (b.getY().toMm() * a.getY().toMm()));
106 qreal e = ((a.getX().toMm() * a.getX().toMm()) +
107 (a.getY().toMm() * a.getY().toMm()));
108 if (a.isOrigin() || b.isOrigin() || (d <= 0.0)) {
109 return l1;
110 } else if (c.isOrigin() || (e <= d)) {
111 return l2;
112 } else {
113 Q_ASSERT(e > 0.0);
114 return l1 + Point::fromMm(a.getX().toMm() * d / e, a.getY().toMm() * d / e);
115 }
116 }
117
shortestDistanceBetweenPointAndLine(const Point & p,const Point & l1,const Point & l2,Point * nearest)118 UnsignedLength Toolbox::shortestDistanceBetweenPointAndLine(
119 const Point& p, const Point& l1, const Point& l2, Point* nearest) noexcept {
120 Point np = nearestPointOnLine(p, l1, l2);
121 if (nearest) {
122 *nearest = np;
123 }
124 return (p - np).getLength();
125 }
126
incrementNumberInString(QString string)127 QString Toolbox::incrementNumberInString(QString string) noexcept {
128 QRegularExpression regex("([0-9]+)(?!.*[0-9]+)");
129 QRegularExpressionMatch match = regex.match(string);
130 if (match.hasMatch()) {
131 // string contains numbers -> increment last number
132 bool ok = false;
133 uint number = match.captured().toUInt(&ok);
134 if (ok) {
135 string.replace(match.capturedStart(), match.capturedLength(),
136 QString::number(number + 1U));
137 return string;
138 }
139 }
140
141 // fallback: just add a "1" at the end
142 return string % "1";
143 }
144
expandRangesInString(const QString & string)145 QStringList Toolbox::expandRangesInString(const QString& string) noexcept {
146 // Do NOT accept '+' and '-', they are considered as strings, not numbers!
147 // For example in the range connector signals range "X-1..10" you expect
148 // numbers starting from 1, not -1.
149 QString number = "\\d+";
150 QString character = "[a-zA-Z]";
151 QString separator = "\\.\\.";
152 QString numberRange =
153 QString("(?<num_start>%1)%2(?<num_end>%1)").arg(number, separator);
154 QString characterRange =
155 QString("(?<char_start>%1)%2(?<char_end>%1)").arg(character, separator);
156 QString pattern =
157 QString("(?<num>%1)|(?<char>%2)").arg(numberRange, characterRange);
158 QRegularExpression re(pattern);
159 QRegularExpressionMatchIterator it = re.globalMatch(string);
160 QVector<std::tuple<int, int, QStringList>> replacements;
161 while (it.hasNext()) {
162 QRegularExpressionMatch match = it.next();
163 QStringList matchReplacements;
164 if (!match.captured("num").isEmpty()) {
165 bool okStart = false, okEnd = false;
166 int start = match.captured("num_start").toInt(&okStart);
167 int end = match.captured("num_end").toInt(&okEnd);
168 if (okStart && okEnd) {
169 bool invert = start > end;
170 if (invert) std::swap(start, end);
171 for (int i = start; i <= end; ++i) {
172 if (invert) {
173 matchReplacements.prepend(QString::number(i));
174 } else {
175 matchReplacements.append(QString::number(i));
176 }
177 }
178 }
179 } else if (!match.captured("char").isEmpty()) {
180 QString startStr = match.captured("char_start");
181 QString endStr = match.captured("char_end");
182 if ((startStr.length()) == 1 && (endStr.length() == 1)) {
183 int start = startStr[0].unicode();
184 int end = endStr[0].unicode();
185 bool invert = start > end;
186 if (invert) std::swap(start, end);
187 if (((start >= 'a') && (end <= 'z')) ||
188 ((start >= 'A') && (end <= 'Z'))) {
189 for (int i = start; i <= end; ++i) {
190 if (invert) {
191 matchReplacements.prepend(QChar::fromLatin1(i));
192 } else {
193 matchReplacements.append(QChar::fromLatin1(i));
194 }
195 }
196 }
197 }
198 }
199 // allow max. 4 replacements to avoid huge results
200 if (!matchReplacements.isEmpty() && (replacements.count() < 4)) {
201 replacements.append(std::make_tuple(
202 match.capturedStart(), match.capturedLength(), matchReplacements));
203 }
204 }
205 return expandRangesInString(string, replacements);
206 }
207
cleanUserInputString(const QString & input,const QRegularExpression & removeRegex,bool trim,bool toLower,bool toUpper,const QString & spaceReplacement,int maxLength)208 QString Toolbox::cleanUserInputString(const QString& input,
209 const QRegularExpression& removeRegex,
210 bool trim, bool toLower, bool toUpper,
211 const QString& spaceReplacement,
212 int maxLength) noexcept {
213 // perform compatibility decomposition (NFKD)
214 QString ret = input.normalized(QString::NormalizationForm_KD);
215 // change case of all characters
216 if (toLower) ret = ret.toLower();
217 if (toUpper) ret = ret.toUpper();
218 // remove leading and trailing spaces
219 if (trim) ret = ret.trimmed();
220 // replace remaining spaces with replacement
221 ret.replace(" ", spaceReplacement);
222 // remove all invalid characters
223 ret.remove(removeRegex);
224 // truncate to maximum allowed length
225 if (maxLength >= 0) ret.truncate(maxLength);
226 // if there are leading or trailing spaces, remove them again ;)
227 if (trim) ret = ret.trimmed();
228 return ret;
229 }
230
prettyPrintLocale(const QString & code)231 QString Toolbox::prettyPrintLocale(const QString& code) noexcept {
232 QLocale locale(code);
233 QString str = locale.nativeLanguageName();
234 if (str.isEmpty()) {
235 str = code; // fallback if language code is not recognized
236 }
237 if (!locale.nativeCountryName().isEmpty()) {
238 str += " (" % locale.nativeCountryName() % ")";
239 }
240 return str;
241 }
242
243 /*******************************************************************************
244 * Private Methods
245 ******************************************************************************/
246
expandRangesInString(const QString & input,const QVector<std::tuple<int,int,QStringList>> & replacements)247 QStringList Toolbox::expandRangesInString(
248 const QString& input,
249 const QVector<std::tuple<int, int, QStringList>>& replacements) noexcept {
250 if (replacements.isEmpty()) {
251 return QStringList{input};
252 } else {
253 QStringList result;
254 int pos = std::get<0>(replacements.first());
255 int len = std::get<1>(replacements.first());
256 foreach (const QString& replacement, std::get<2>(replacements.first())) {
257 foreach (QString str, expandRangesInString(input, replacements.mid(1))) {
258 result.append(str.replace(pos, len, replacement));
259 }
260 }
261 return result;
262 }
263 }
264
265 /*******************************************************************************
266 * End of File
267 ******************************************************************************/
268
269 } // namespace librepcb
270