GCC Code Coverage Report


source/XpertMassCore/src/
File: source/XpertMassCore/src/Utils.cpp
Date: 2025-11-20 01:41:33
Lines:
45/230
19.6%
Functions:
6/26
23.1%
Branches:
37/298
12.4%

Line Branch Exec Source
1 /* BEGIN software license
2 *
3 * MsXpertSuite - mass spectrometry software suite
4 * -----------------------------------------------
5 * Copyright (C) 2009--2024 Filippo Rusconi
6 *
7 * http://www.msxpertsuite.org
8 *
9 * This file is part of the MsXpertSuite project.
10 *
11 * The MsXpertSuite project is the successor of the massXpert project. This
12 * project now includes various independent modules:
13 *
14 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
15 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
16 *
17 * This program is free software: you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation, either version 3 of the License, or
20 * (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with this program. If not, see <http://www.gnu.org/licenses/>.
29 *
30 * END software license
31 */
32
33
34 ////////////////////////////// Stdlib includes
35 #include <math.h>
36 #include <cstdio>
37
38
39 ////////////////////////////// Qt includes
40 #include <QDebug>
41 #include <QStandardPaths>
42 #include <QDir>
43 #include <QProcess>
44
45
46 ////////////////////////////// Local includes
47 #include "MsXpS/libXpertMassCore/Utils.hpp"
48
49 namespace MsXpS
50 {
51 namespace libXpertMassCore
52 {
53
54
55 /*!
56 \class MsXpS::libXpertMassCore::Utils
57 \inmodule libXpertMassCore
58 \ingroup XpertMassCoreUtilities
59 \inheaderfile Utils.hpp
60
61 \brief The Utils class provides a number of utilitary features that may be of
62 use anywhere in the XpertMass source code tree.
63 */
64
65
66 /*!
67 \variable MsXpS::libXpertMassCore::Utils::subFormulaRegExp
68
69 \brief Regular expression used to deconstruct the main actionformula into
70 minus and plus component subformulas.
71
72 This regular expression does not account for the \"<text>\" title
73 of the actionformula.
74
75 \code
76 "([+-]?)([A-Z][a-z]*)(\\d*[\\.]?\\d*)"
77 \endcode
78
79 \sa Formula::splitActionParts()
80 */
81 QRegularExpression Utils::subFormulaRegExp =
82 QRegularExpression(QString("([+-]?)([A-Z][a-z]*)(\\d*[\\.]?\\d*)"));
83
84 /*!
85 \variable MsXpS::libXpertMassCore::Utils::xyFormatMassDataRegExp
86
87 \brief Regular expression that matches the m/z,i pairs in text files.
88 */
89 QRegularExpression Utils::xyFormatMassDataRegExp =
90 QRegularExpression("^(\\d*\\.?\\d+)([^\\d^\\.^-]+)(-?\\d*\\.?\\d*[e-]?\\d*)");
91
92 /*!
93 \variable MsXpS::libXpertMassCore::Utils::endOfLineRegExp
94
95 \brief Regular expression that matches the end of line in text files.
96 */
97 QRegularExpression Utils::endOfLineRegExp = QRegularExpression("^\\s+$");
98
99
100 /*!
101 \variable MsXpS::libXpertMassCore::Utils::xmlIndentationToken
102
103 \brief String used to craft the indentation of the XML elements.
104 */
105 QString Utils::xmlIndentationToken = QString(" ");
106
107 /*!
108 \brief Constructs a Utils instance setting parent to \a parent.
109 */
110 164853 Utils::Utils(QObject *parent): QObject(parent)
111 {
112 164853 }
113
114 /*!
115 \brief Destructs a Utils instance.
116 */
117 329706 Utils::~Utils()
118 {
119 329706 }
120
121 /*!
122 \brief Configures the format of all the messages that are output using qInfo(),
123 qWarning(), qCritical(), qFatalStream() and qDebug() using a number of
124 parameters.
125
126 \list
127 \li \a type The kind of message
128
129 \li \a context The context of the message
130
131 \li \a msg The message to output.
132 \endlist
133 */
134 void
135 453599 Utils::messageOutputFormat(QtMsgType type,
136 const QMessageLogContext &context,
137 const QString &msg)
138 {
139 // Define ANSI color codes
140 #define RESET "\033[0m"
141 #define BLACK "\033[30m"
142 #define RED "\033[31m"
143 #define GREEN "\033[32m"
144 #define YELLOW "\033[33m"
145 #define BLUE "\033[34m"
146 #define MAGENTA "\033[35m"
147 #define CYAN "\033[36m"
148 #define WHITE "\033[36m"
149 #define BOLD "\033[1m"
150
151 453599 QByteArray localMsg = msg.toLocal8Bit();
152
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 453599 times.
453599 const char *file = context.file ? context.file : "";
153
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 453599 times.
453599 const char *function = context.function ? context.function : "";
154
155 453599 QString prefix;
156 453599 QString color;
157
158
159
3/6
✓ Branch 0 taken 432 times.
✓ Branch 1 taken 112679 times.
✓ Branch 2 taken 340488 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
453599 switch(type)
160 {
161 432 case QtInfoMsg:
162
1/2
✓ Branch 1 taken 432 times.
✗ Branch 2 not taken.
432 prefix = "INFO: ";
163
3/6
✓ Branch 1 taken 432 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 432 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 432 times.
✗ Branch 8 not taken.
432 color = QString("%1").arg(GREEN);
164 432 break;
165 112679 case QtWarningMsg:
166
1/2
✓ Branch 1 taken 112679 times.
✗ Branch 2 not taken.
112679 prefix = "WARNING: ";
167
3/6
✓ Branch 1 taken 112679 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 112679 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 112679 times.
✗ Branch 8 not taken.
112679 color = QString("%1").arg(BLUE);
168 112679 break;
169 340488 case QtCriticalMsg:
170
1/2
✓ Branch 1 taken 340488 times.
✗ Branch 2 not taken.
340488 prefix = "CRITICAL: ";
171
3/6
✓ Branch 1 taken 340488 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 340488 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 340488 times.
✗ Branch 8 not taken.
340488 color = QString("%1").arg(MAGENTA);
172 340488 break;
173 case QtFatalMsg:
174 prefix = "FATAL: ";
175 color = QString("%1").arg(RED);
176 break;
177 case QtDebugMsg:
178 prefix = "DEBUG: ";
179 color = QString("%1").arg(YELLOW);
180 break;
181 }
182
183
5/10
✓ Branch 0 taken 453599 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 453599 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 453599 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 453599 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 453599 times.
✗ Branch 13 not taken.
2267995 fprintf(stderr,
184 "%s%s%s%s:%s%u%s\n%s\n%s======> %s%s\n\n",
185
1/2
✓ Branch 0 taken 453599 times.
✗ Branch 1 not taken.
907198 color.toLocal8Bit().constData(),
186
1/2
✓ Branch 0 taken 453599 times.
✗ Branch 1 not taken.
907198 prefix.toLocal8Bit().constData(),
187 RESET,
188 file,
189
1/2
✓ Branch 0 taken 453599 times.
✗ Branch 1 not taken.
907198 color.toLocal8Bit().constData(),
190
1/2
✓ Branch 1 taken 453599 times.
✗ Branch 2 not taken.
453599 context.line,
191 RESET,
192 function,
193
1/2
✓ Branch 0 taken 453599 times.
✗ Branch 1 not taken.
907198 color.toLocal8Bit().constData(),
194 localMsg.constData(),
195 RESET);
196 453599 }
197
198 /*!
199 \brief Installs the message handler.
200 \sa Utils::messageOutputFormat()
201 */
202 void
203 275 Utils::configureDebugMessagesFormat()
204 {
205 275 qInstallMessageHandler(messageOutputFormat);
206 275 }
207
208 /*!
209 \brief Returns the average of the \a list of \c double values.
210
211 All the values in list are process in order to compute the following:
212
213 \list
214 \li \a sum
215 \li \a average
216 \li \a variance
217 \li \a std_dev
218 \li \a smallest_non_zero
219 \li \a smallest
220 \li \a smallest_median
221 \li \a greatest
222 \endlist
223
224 statistical values if the corresponding parameters are non-nullptr.
225
226 \sa doubleVectorStatistics()
227 */
228 void
229 Utils::doubleListStatistics(QList<double> list,
230 double &sum,
231 double &average,
232 double &variance,
233 double &std_dev,
234 double &smallest_non_zero,
235 double &smallest,
236 double &smallest_median,
237 double &greatest)
238 {
239 // Sort the list, we'll need it sorted to compute the median value.
240 std::sort(list.begin(), list.end());
241
242 int count = list.size();
243
244 if(count == 0)
245 return;
246
247 // Sorted list, the smallest is the first.
248 smallest = list.first();
249 // The greatest is the last.
250 greatest = list.last();
251
252 sum = 0;
253 smallest_non_zero = std::numeric_limits<double>::max();
254
255 for(int iter = 0; iter < count; ++iter)
256 {
257 double current = list.at(iter);
258 sum += current;
259
260 // If current is non-zero, then take its value into account.
261 if(current > 0 && current < smallest_non_zero)
262 smallest_non_zero = current;
263 }
264
265 average = sum / count;
266
267 double varN = 0;
268
269 for(int iter = 0; iter < count; ++iter)
270 {
271 varN += (list.at(iter) - average) * (list.at(iter) - average);
272 }
273
274 variance = varN / count;
275 std_dev = std::sqrt(variance);
276
277 // Now the median value
278
279 // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
280 //<< "count:" << count;
281
282 if(count == 1)
283 {
284 smallest_median = list.first();
285 }
286 else
287 {
288 if(count % 2 == 0)
289 smallest_median = (list.at(count / 2 - 1) + list.at(count / 2)) / 2;
290 else
291 smallest_median = list.at(count / 2 + 1);
292 }
293 }
294
295 /*!
296 \brief Returns the average of the \a vector of \c double values.
297
298 All the values in the container are processed in order to compute the following:
299
300 \list
301 \li \a sum
302 \li \a average
303 \li \a variance
304 \li \a std_dev
305 \li \a smallest_non_zero
306 \li \a smallest
307 \li \a smallest_median
308 \li \a greatest
309 \endlist
310
311 statistical values if the corresponding parameters are non-nullptr.
312
313 \sa doubleListStatistics()
314 */
315 void
316 Utils::doubleVectorStatistics(std::vector<double> &vector,
317 double &sum,
318 double &average,
319 double &variance,
320 double &std_dev,
321 double &smallest_non_zero,
322 double &smallest,
323 double &smallest_median,
324 double &greatest)
325 {
326 // Sort the vector, we'll need it sorted to compute the median value.
327 std::sort(vector.begin(), vector.end());
328
329 int count = vector.size();
330
331 if(!count)
332 return;
333
334 // Sorted vector, the smallest is the first.
335 smallest = vector.front();
336 // The greatest is the last.
337 greatest = vector.back();
338
339 sum = 0;
340 smallest_non_zero = std::numeric_limits<double>::max();
341
342 for(double &value : vector)
343 {
344 double current = value;
345 sum += current;
346
347 // If current is non-zero, then take its value into account.
348 if(current > 0 && current < smallest_non_zero)
349 smallest_non_zero = current;
350 }
351
352 average = sum / count;
353
354 double varN = 0;
355
356 for(auto &value : vector)
357 {
358 varN += (value - average) * (value - average);
359 }
360
361 variance = varN / count;
362 std_dev = std::sqrt(variance);
363
364 // Now the median value
365
366 // qDebug() << "count:" << count;
367
368 if(count == 1)
369 {
370 smallest_median = vector.front();
371 }
372 else
373 {
374 if(count % 2 == 0)
375 smallest_median = (vector.at(count / 2 - 1) + vector.at(count / 2)) / 2;
376 else
377 smallest_median = vector.at(count / 2 + 1);
378 }
379 }
380
381 /*!
382 \brief Returns true if both double values \a value1 and \a value2, are equal
383 within the double representation capabilities of the platform, false otherwise.
384
385 In the comparison, the \a decimal_places are taken into account.
386 */
387 bool
388 Utils::almostEqual(double value1, double value2, int decimal_places)
389 {
390 // QString value1String = QString("%1").arg(value1,
391 // 0, 'f', 60);
392 // QString value2String = QString("%1").arg(value2,
393 // 0, 'f', 60);
394
395 // qWarning() << __FILE__ << __LINE__ << __FUNCTION__
396 //<< "value1:" << value1String << "value2:" << value2String;
397
398 // The machine epsilon has to be scaled to the magnitude of the values used
399 // and multiplied by the desired precision in ULPs (units in the last place)
400 // (decimal places).
401
402 double sum_of_values = std::abs(value1 + value2);
403 // QString sum_of_valuesString = QString("%1").arg(sum_of_values,
404 // 0, 'f', 60);
405
406 double diff_of_values = std::abs(value1 - value2);
407 // QString diff_of_valuesString = QString("%1").arg(diff_of_values,
408 // 0, 'f', 60);
409
410 double epsilon = std::numeric_limits<double>::epsilon();
411 // QString epsilonString = QString("%1").arg(epsilon,
412 // 0, 'f', 60);
413
414 double scale_factor = epsilon * sum_of_values * decimal_places;
415 // QString scale_factorString = QString("%1").arg(scale_factor,
416 // 0, 'f', 60);
417
418 // qWarning() << "diff_of_values:" << diff_of_valuesString << "sum_of_values:"
419 // << sum_of_valuesString << "epsilon:" << epsilonString << "scale_factor:" <<
420 // scale_factorString;
421
422 bool res = diff_of_values < scale_factor
423 // unless the result is subnormal:
424 || diff_of_values < std::numeric_limits<double>::min();
425
426 // qWarning() << __FILE__ << __LINE__ << __FUNCTION__
427 //<< "returning res:" << res;
428
429 return res;
430 }
431
432 /*!
433 \brief Provides a text stream handle to the standard output.
434
435 Use:
436
437 \code
438 qStdOut() << __FILE__ << __LINE__
439 << "text to the standard output,"
440 << "not the error standard output."
441 \endcode
442
443 Returns a reference to a static QTextStream that directs to the standard
444 output.
445 */
446 QTextStream &
447 Utils::qStdOut()
448 {
449 static QTextStream ts(stdout);
450 return ts;
451 }
452
453 /*!
454 \brief Removes all space characters from a copy of the \a text string and
455 returns the new string.
456 */
457 QString
458 142 Utils::unspacify(const QString &text)
459 {
460
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 142 times.
142 if(text.isEmpty())
461 {
462
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
142 return text;
463 }
464
465
1/2
✓ Branch 0 taken 142 times.
✗ Branch 1 not taken.
142 QString unspacified = text;
466
3/6
✓ Branch 1 taken 142 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 142 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 142 times.
✗ Branch 8 not taken.
142 unspacified.remove(QRegularExpression("\\s+"));
467
468 142 return unspacified;
469 142 }
470
471 /*!
472 \brief Returns a string with a binary representation of the \a value integer.
473 */
474 QString
475 Utils::binaryRepresentation(int value)
476 {
477 QString string;
478 string = QString("%1").arg(value, 32, 2);
479
480 return string;
481 }
482
483 /*!
484 \brief Returns a shortened (elided) version of the \a text string.
485
486 It is sometimes necessary to display, in a graphical user interface a very long
487 string, that cannot fit in the provided widget. This function returns a
488 shortened version of the input string.
489
490 For example, "Interesting bits of information are often lost where there
491 are too many details", becomes "Interes...details".
492
493 \a chars_left: Count of characters to be kept on the left side of the string.
494
495 \a chars_right: Count of characters to be kept on the right side of the string.
496
497 \a delimiter string to use as the elision delimiter (in the above example, that
498 is '.').
499 */
500 QString
501 Utils::elideText(const QString &text,
502 int chars_left,
503 int chars_right,
504 const QString &delimiter)
505 {
506 // We want to elide text. For example, imagine we have text = "that
507 // is beautiful stuff", with charsLeft 4 and charsRight 4 and
508 // delimitor "...". Then the result would be "that...tuff"
509
510 if(chars_left < 1 || chars_right < 1)
511 {
512 return text;
513 }
514
515 int size = text.size();
516
517 // If the text string is already too short, no need to elide
518 // anything.
519 if((chars_left + chars_right + delimiter.size()) >= size)
520 {
521 return text;
522 }
523
524 QString result = text.left(chars_left);
525 result.append(delimiter);
526 result.append(text.right(chars_right));
527
528 return result;
529 }
530
531 /*!
532 \brief Returns a string containing a paragraph-based version of the very long
533 single-line \a text.
534
535 When a text string is too long to be displayed in a line of reasonable
536 length, inserts newline characters at positions calculated to yield a
537 paragraph of the given \a width.
538
539 \sa stanzifyParagraphs()
540 */
541 QString
542 Utils::stanzify(const QString &text, int width)
543 {
544 QString result = text;
545
546 // First, replace all the existing newline chars with spaces.
547
548 result = result.replace("\n", " ");
549
550 int iter = width;
551
552 // Then, iterate in the obtained string and every width characters try to
553 // insert a newline character by iterating back to the left and searching
554 // for a space.
555
556 for(; iter < result.size(); iter += width)
557 {
558 // Now iterate in reverse and search for a space where to insert a
559 // newline
560
561 int jter = iter;
562
563 for(; jter >= 0; --jter)
564 {
565 if(result.at(jter) == ' ')
566 {
567 result[jter] = '\n';
568 break;
569 }
570 }
571 }
572
573 return result;
574 }
575
576 /*!
577 \brief Returns a string containing a series of paragraph-based versions of the
578 very long single-line-containing paragraphs in \a text.
579
580 \a text is a string with newline characters that delimit paragraph that
581 thelmselves are made of a very long single line. This function splits \a text
582 along the \c '\n' character and each obtained very long single-line string is
583 reduced to a set of lines to make a pararagraph (see \l{stanzify}). The with of
584 the line in that generated paragraph is \a width.
585
586 \sa stanzify()
587 */
588 QString
589 Utils::stanzifyParagraphs(const QString &text, int width)
590 {
591 QString result;
592
593 QStringList paragraphList = text.split("\n");
594
595 for(int iter = 0; iter < paragraphList.size(); ++iter)
596 {
597 QString line = paragraphList.at(iter);
598
599 QString stanzifiedLine = stanzify(line, width);
600
601 result.append(stanzifiedLine);
602 result.append("\n");
603 }
604
605 return result;
606 }
607
608 /*!
609 \brief Returns the number of zero decimals in \a value that are found between
610 the decimal point and the first non-zero decimal.
611
612 For example, 0.11 would return 0 (no empty decimal)
613
614 2.001 would return 2
615
616 1000.0001254 would return 3
617 */
618 int
619 Utils::countZeroDecimals(double value)
620 {
621
622 int intPart = static_cast<int>(value);
623
624 double decimalPart = value - intPart;
625
626 int count = -1;
627
628 while(1)
629 {
630 ++count;
631
632 decimalPart *= 10;
633
634 if(decimalPart > 1)
635 return count;
636 }
637
638 return count;
639 }
640
641 /*!
642 \brief Returns the delta value corresponding to \a value and \a ppm
643 part-per-million.
644 */
645 double
646 Utils::ppm(double value, double ppm)
647 {
648
649 return (value * ppm) / 1000000;
650 }
651
652 /*!
653 \brief Returns \a value incremented by the matching \a ppm part.
654 */
655 double
656 Utils::addPpm(double value, double ppm)
657 {
658
659 return value + (value * ppm) / 1000000;
660 }
661
662 /*!
663 \brief Returns \a value decremented by the matching \a ppm part.
664 */
665 double
666 Utils::removePpm(double value, double ppm)
667 {
668 return value - (value * ppm) / 1000000;
669 }
670
671 /*!
672 \brief Return the delta corresponding to \a value and resolution \a res.
673 */
674 double
675 Utils::res(double value, double res)
676 {
677 return value / res;
678 }
679
680 /*!
681 \brief Returns \a value incremented by the matching \a res part.
682 */
683 double
684 Utils::addRes(double value, double res)
685 {
686 return value + (value / res);
687 }
688
689 /*!
690 \brief Returns \a value decremented by the matching \a res part.
691 */
692 double
693 Utils::removeRes(double value, double res)
694 {
695 return value - (value / res);
696 }
697
698 /*!
699 \fn template <typename T> QString Utils::pointerAsString(T *ptr)
700
701 \brief Returns a string representing the \a ptr address location.
702
703 \sa stringAsPointer
704 */
705 template <typename T>
706 QString
707 Utils::pointerAsString(T *ptr)
708 {
709 return QString::number(reinterpret_cast<quintptr>(ptr),
710 16); // Convert to hex string
711 }
712
713 /*!
714 \fn template <typename T> T * Utils::stringAsPointer(const QString &str)
715
716 \brief Returns a pointer to type T corresponding the \a str representation of
717 the pointer.
718
719 \sa pointerAsString()
720 */
721 template <typename T>
722 T *
723 Utils::stringAsPointer(const QString &str)
724 {
725 bool ok;
726 quintptr intPtr =
727 str.toULongLong(&ok, 16); // Convert hex string back to integer
728 return ok ? reinterpret_cast<T *>(intPtr) : nullptr;
729 }
730
731 /*!
732 \brief Returns a string holding the the file path to the configuration settings
733 for application \a module_name.
734 */
735 QString
736 Utils::craftConfigSettingsFilePath(const QString &module_name)
737 {
738
739 // The configuration settings file for all the settings of the program
740
741 QString file_path =
742 QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
743
744 if(file_path.isEmpty())
745 file_path = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
746
747 file_path = file_path + QDir::separator() + module_name;
748
749 file_path = file_path + QDir::separator() + "configSettings.ini";
750
751 return file_path;
752 }
753
754 /*!
755 \brief Returns 0 if both files \a file_path_1 and \a file_path_2 have the same
756 contents or 1 if theirs contents differ.
757
758 Returns -1 if any file could not be opened.
759 */
760 int
761 Utils::testDiffBetweenTwoTextFiles(const QString &file_path_1,
762 const QString &file_path_2)
763 {
764 QFile file_1(file_path_1);
765 if(!file_1.open(QIODevice::ReadOnly | QIODevice::Text))
766 {
767 qDebug() << file_path_1 << "could not be opened";
768 return -1;
769 }
770
771 QFile file_2(file_path_2);
772 if(!file_2.open(QIODevice::ReadOnly | QIODevice::Text))
773 {
774 qDebug() << file_path_2 << "could not be opened";
775 return -1;
776 }
777
778 QTextStream in_stream_1(&file_1), in_stream_2(&file_2);
779
780 while(!in_stream_1.atEnd() && !in_stream_2.atEnd())
781 {
782 QString line_1 = in_stream_1.readLine();
783 QString line_2 = in_stream_2.readLine();
784 if(line_1 != line_2)
785 return 1;
786 }
787
788 return 0;
789 }
790
791 /*!
792 \brief Returns a string obtained by concatenating all the strings in the \a
793 error_list container. Each string is separated from the other with \a separator.
794 */
795 QString
796 30534 Utils::joinErrorList(const ErrorList &error_list, const QString &separator)
797 {
798 30534 QString text;
799
800
2/2
✓ Branch 0 taken 161868 times.
✓ Branch 1 taken 30534 times.
192402 for(const QString &string : error_list)
801
3/6
✓ Branch 1 taken 161868 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 161868 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 161868 times.
✗ Branch 8 not taken.
323736 text += QString("%1%2").arg(string).arg(separator);
802
803 30534 return text;
804 }
805
806
807 #ifdef Q_OS_WIN
808 #include <windows.h>
809 #include <psapi.h>
810 #include <tlhelp32.h>
811
812 bool
813 Utils::isProcessRunningInWindows(const QString &process_name)
814 {
815 bool isRunning = false;
816 PROCESSENTRY32 entry;
817 entry.dwSize = sizeof(PROCESSENTRY32);
818
819 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
820
821 if(Process32First(snapshot, &entry))
822 {
823 while(Process32Next(snapshot, &entry))
824 {
825 if(QString::fromWCharArray(entry.szExeFile) == process_name)
826 {
827 isRunning = true;
828 break;
829 }
830 }
831 }
832
833 CloseHandle(snapshot);
834 return isRunning;
835 }
836 #endif
837
838 #ifdef Q_OS_LINUX
839 #include <QDir>
840 #include <QFile>
841
842 bool
843 Utils::isProcessRunningInLinux(const QString &process_name)
844 {
845 QDir procDir("/proc");
846 QStringList processes = procDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
847
848 for(const QString &pid : processes)
849 {
850 // Check if it's a valid PID (numeric)
851 bool ok;
852 pid.toInt(&ok);
853 if(!ok)
854 continue;
855
856 QFile cmdlineFile(QString("/proc/%1/comm").arg(pid));
857 if(cmdlineFile.open(QIODevice::ReadOnly))
858 {
859 QString name =
860 QString::fromLocal8Bit(cmdlineFile.readAll()).trimmed();
861 if(name == process_name)
862 {
863 return true;
864 }
865 }
866 }
867 return false;
868 }
869 #endif
870
871 bool
872 Utils::isProgramRunning(const QString &program_name)
873 {
874 // Try the QProcess method first (most portable)
875 QProcess process;
876
877 #ifdef Q_OS_WIN
878 process.start("tasklist",
879 QStringList() << "/NH" << "/FI"
880 << QString("IMAGENAME eq %1").arg(program_name));
881 #else
882 process.start("pgrep", QStringList() << "-x" << program_name);
883 #endif
884
885 if(process.waitForFinished(1000))
886 {
887 QString output = process.readAllStandardOutput();
888
889 #ifdef Q_OS_WIN
890 return output.contains(program_name, Qt::CaseInsensitive);
891 #else
892 return !output.trimmed().isEmpty();
893 #endif
894 }
895
896 // Fallback to platform-specific methods if QProcess fails
897 #ifdef Q_OS_WIN
898 return isProcessRunningInWindows(program_name);
899 #elif defined(Q_OS_LINUX)
900 return isProcessRunningInLinux(program_name);
901 #else
902 return false; // macOS or other platforms
903 #endif
904 }
905
906 void
907 Utils::registerJsConstructor(QJSEngine *engine)
908 {
909 if(!engine)
910 {
911 qWarning() << "Cannot register Utils class: engine is null";
912 return;
913 }
914
915 // Register the meta object as a constructor
916 qDebug() << "Registering JS constructor for class Utils.";
917
918 // The registration below makes it possible to allocate a Utils
919 // instance using var the_utils = new Utils()
920 // Then the functions are used as the_utils.unspacify("This text has spaces")
921 QJSValue jsMetaObject = engine->newQMetaObject(&Utils::staticMetaObject);
922 engine->globalObject().setProperty("Utils", jsMetaObject);
923
924 // The registration below creates a singleton exposed as "utils" which
925 // makes it possible to use the static functions as
926 // utils.unspacify("This text has spaces") without first allocating
927 // an instance of the class.
928 engine->globalObject().setProperty("utils",
929 engine->newQObject(new Utils(engine)));
930 }
931
932
933 } // namespace libXpertMassCore
934 } // namespace MsXpS
935