1 #include "utilsSystem.h"
2 #include "unixutils.h"
3 #include "smallUsefulFunctions.h"
4
5 #ifdef Q_OS_MAC
6 #include <CoreFoundation/CFURL.h>
7 #include <CoreFoundation/CFBundle.h>
8 #endif
9
10 #ifdef Q_OS_WIN
11 #include <windows.h>
12 #endif
getDiskFreeSpace(const QString & path,quint64 & freeBytes)13 bool getDiskFreeSpace(const QString &path, quint64 &freeBytes)
14 {
15 #ifdef Q_OS_WIN
16 wchar_t* d = new wchar_t[path.size() + 1];
17 int len = path.toWCharArray(d);
18 d[len] = 0;
19
20 ULARGE_INTEGER freeBytesToCaller;
21 freeBytesToCaller.QuadPart = 0L;
22
23 if ( !GetDiskFreeSpaceEx( d, &freeBytesToCaller, NULL, NULL ) ) {
24 delete[] d;
25 qDebug() << "ERROR: Call to GetDiskFreeSpaceEx() failed on path" << path;
26 return false;
27 }
28 delete[] d;
29 freeBytes = freeBytesToCaller.QuadPart;
30 return true;
31 #else
32 Q_UNUSED(path)
33 Q_UNUSED(freeBytes)
34 return false;
35 #endif
36 }
37
getKeyboardLanguage()38 QLocale::Language getKeyboardLanguage() {
39 return QGuiApplication::inputMethod()->locale().language();
40 }
41
42 /*!
43 * Redefine or filter some shortcuts depending on the locale
44 * On Windows, AltGr is interpreted as Ctrl+Alt so we shouldn't
45 * use a Ctrl+Alt+Key shortcut if AltGr+Key is used for typing
46 * characters.
47 */
filterLocaleShortcut(QKeySequence ks)48 QKeySequence filterLocaleShortcut(QKeySequence ks)
49 {
50 #ifndef Q_OS_WIN32
51 return ks;
52 #else
53 QLocale::Language lang = getKeyboardLanguage();
54 switch (lang) {
55 case QLocale::Hungarian:
56 if (ks.matches(QKeySequence("Ctrl+Alt+F"))) {
57 return QKeySequence("Ctrl+Alt+Shift+F");
58 }
59 break;
60 case QLocale::Polish:
61 if (ks.matches(QKeySequence("Ctrl+Alt+S"))) {
62 return QKeySequence();
63 } else if (ks.matches(QKeySequence("Ctrl+Alt+U"))) {
64 return QKeySequence("Ctrl+Alt+Shift+U");
65 }
66 break;
67 case QLocale::Turkish:
68 if (ks.matches(QKeySequence("Ctrl+Alt+F"))) {
69 return QKeySequence("Ctrl+Alt+Shift+F");
70 }
71 break;
72 case QLocale::Czech:
73 if (ks.matches(QKeySequence("Ctrl+Alt+S"))) {
74 return QKeySequence();
75 } else if (ks.matches(QKeySequence("Ctrl+Alt+F"))) {
76 return QKeySequence("Ctrl+Alt+Shift+F");
77 } else if (ks.matches(QKeySequence("Ctrl+Alt+L"))) {
78 return QKeySequence("Ctrl+Alt+Shift+L");
79 }
80 break;
81 case QLocale::Croatian:
82 if (ks.matches(QKeySequence("Ctrl+Alt+F"))) {
83 return QKeySequence("Ctrl+Alt+Shift+F");
84 }
85 break;
86 default:
87 return ks;
88 }
89 return ks;
90 #endif
91 }
92
getPathListSeparator()93 QChar getPathListSeparator()
94 {
95 #ifdef Q_OS_WIN32
96 return QChar(';');
97 #else
98 return QChar(':');
99 #endif
100 }
101
splitPaths(const QString & paths)102 QStringList splitPaths(const QString &paths)
103 {
104 if (paths.isEmpty()) return QStringList();
105 return paths.split(getPathListSeparator());
106 }
107
getUserName()108 QString getUserName()
109 {
110 #ifdef Q_OS_WIN32
111 return QString(qgetenv("USERNAME"));
112 #else
113 return QString(qgetenv("USER"));
114 #endif
115 }
116
getUserDocumentFolder()117 QString getUserDocumentFolder()
118 {
119 #ifdef Q_OS_WIN32
120 // typically "C:/Documents and Settings/Username/My Documents"
121 QSettings settings(QSettings::UserScope, "Microsoft", "Windows");
122 settings.beginGroup("CurrentVersion/Explorer/Shell Folders");
123 return settings.value("Personal").toString();
124 #else
125 return QDir::homePath();
126 #endif
127 }
128
findResourceFiles(const QString & dirName,const QString & filter,QStringList additionalPreferredPaths)129 QStringList findResourceFiles(const QString &dirName, const QString &filter, QStringList additionalPreferredPaths)
130 {
131 QStringList searchFiles;
132 QString dn = dirName;
133 if (dn.endsWith('/') || dn.endsWith(QDir::separator())) dn = dn.left(dn.length() - 1); //remove / at the end
134 if (!dn.startsWith('/') && !dn.startsWith(QDir::separator())) dn = "/" + dn; //add / at beginning
135 searchFiles << ":" + dn; //resource fall back
136 searchFiles.append(additionalPreferredPaths);
137 searchFiles << QCoreApplication::applicationDirPath() + "/../share/texstudio"; //appimage relative path
138 searchFiles << QCoreApplication::applicationDirPath() + dn; //windows new
139 searchFiles << QCoreApplication::applicationDirPath() + "/"; //windows old
140 searchFiles << QCoreApplication::applicationDirPath() + "/dictionaries/"; //windows new
141 searchFiles << QCoreApplication::applicationDirPath() + "/translations/"; //windows new
142 searchFiles << QCoreApplication::applicationDirPath() + "/help/"; //windows new
143 searchFiles << QCoreApplication::applicationDirPath() + "/utilities/"; //windows new
144 // searchFiles<<QCoreApplication::applicationDirPath() + "/data/"+fileName; //windows new
145
146 #if !defined(PREFIX)
147 #define PREFIX ""
148 #endif
149
150 #if defined( Q_WS_X11 ) || defined (Q_OS_LINUX)
151 searchFiles << PREFIX"/share/texstudio" + dn; //X_11
152 #endif
153 #ifdef Q_OS_MAC
154 CFURLRef appUrlRef = CFBundleCopyBundleURL(CFBundleGetMainBundle());
155 CFStringRef macPath = CFURLCopyFileSystemPath(appUrlRef,
156 kCFURLPOSIXPathStyle);
157 const char *pathPtr = CFStringGetCStringPtr(macPath,
158 CFStringGetSystemEncoding());
159 searchFiles << QString(pathPtr) + "/Contents/Resources" + dn; //Mac
160 CFRelease(appUrlRef);
161 CFRelease(macPath);
162 #endif
163
164 QStringList result;
165 foreach (const QString &fn, searchFiles) {
166 QDir fic(fn);
167 if (fic.exists() && fic.isReadable())
168 result << fic.entryList(QStringList(filter), QDir::Files, QDir::Name);
169 }
170 // sort and remove double entries
171 result.sort();
172
173 QMutableStringListIterator i(result);
174 QString old = "";
175 while (i.hasNext()) {
176 QString cmp = i.next();
177 if (cmp == old) i.remove();
178 else old = cmp;
179 }
180 return result;
181 }
182
findResourceFile(const QString & fileName,bool allowOverride,QStringList additionalPreferredPaths,QStringList additionalFallbackPaths)183 QString findResourceFile(const QString &fileName, bool allowOverride, QStringList additionalPreferredPaths, QStringList additionalFallbackPaths)
184 {
185 QStringList searchFiles;
186
187 if (!allowOverride) searchFiles << ":/"; //search first in included resources (much faster)
188
189 foreach (const QString &s, additionalPreferredPaths)
190 if (s.endsWith('/') || s.endsWith('\\')) searchFiles << s;
191 else searchFiles << s + "/";
192 #if defined Q_WS_X11 || defined Q_OS_LINUX || defined Q_OS_UNIX
193 searchFiles << PREFIX"/share/texstudio/"; //X_11
194 searchFiles << QCoreApplication::applicationDirPath() + "/../share/texstudio/"; // relative path for appimage
195 if (fileName.endsWith(".html")) searchFiles << PREFIX"/share/doc/texstudio/html/"; //for Debian package
196 #endif
197 #ifdef Q_OS_MAC
198 searchFiles << QCoreApplication::applicationDirPath() + "/../Resources/"; //macx
199 #endif
200 searchFiles << QCoreApplication::applicationDirPath() + "/"; //windows old
201 searchFiles << QCoreApplication::applicationDirPath() + "/dictionaries/"; //windows new
202 searchFiles << QCoreApplication::applicationDirPath() + "/translations/"; //windows new
203 searchFiles << QCoreApplication::applicationDirPath() + "/help/"; //windows new
204 searchFiles << QCoreApplication::applicationDirPath() + "/utilities/"; //windows new
205 // searchFiles<<QCoreApplication::applicationDirPath() + "/data/"; //windows new
206
207 if (allowOverride) searchFiles << ":/"; //resource fall back
208
209 foreach (const QString &s, additionalFallbackPaths)
210 if (s.endsWith('/') || s.endsWith('\\')) searchFiles << s;
211 else searchFiles << s + "/";
212
213 foreach (const QString &fn, searchFiles) {
214 QFileInfo fic(fn + fileName);
215 if (fic.exists() && fic.isReadable())
216 return fic.canonicalFilePath();
217 }
218 QString newFileName = fileName.split("/").last();
219 if (!newFileName.isEmpty()) {
220 foreach (const QString &fn, searchFiles) {
221 QFileInfo fic(fn + newFileName);
222 if (fic.exists() && fic.isReadable())
223 return fic.canonicalFilePath();
224 }
225 }
226 return "";
227 }
228
229 /*!
230 * \brief put quotes araound strin when necessary
231 *
232 * Enclose the given string with quotes if it contains spaces.
233 * This function is useful
234 * \param s input string
235 * \return string with quotes (if s contains spaces)
236 */
quoteSpaces(const QString & s)237 QString quoteSpaces(const QString &s)
238 {
239 if (!s.contains(' ')) return s;
240 return '"' + s + '"';
241 }
242
243
244 int modernStyle;
245 bool darkMode;
246 bool useSystemTheme;
247
248 /*!
249 * \brief return icon according to settings
250 *
251 * The icon is looked up in txs resources. It prefers svg over png.
252 * In case of active dark mode, icons with name icon_dm is used (if present).
253 * \param icon icon name
254 * \return found icon as file name
255 */
getRealIconFile(const QString & icon)256 QString getRealIconFile(const QString &icon)
257 {
258 if (icon.isEmpty() || icon.startsWith(":/")) return icon;
259 QStringList suffixList{""};
260 if(darkMode)
261 suffixList=QStringList{"_dm",""};
262 QStringList iconNames = QStringList();
263 for(const QString& suffix : suffixList){
264 iconNames
265 << ":/images-ng/" + icon + suffix + ".svg"
266 << ":/images-ng/" + icon + suffix + ".svgz" ;
267 if (modernStyle) {
268 iconNames << ":/images-ng/modern/" + icon + suffix + ".svg"
269 << ":/images-ng/modern/" + icon + suffix + ".svgz"
270 << ":/modern/images/modern/" + icon + suffix + ".png";
271 } else {
272 iconNames << ":/images-ng/classic/" + icon + suffix + ".svg"
273 << ":/images-ng/classic/" + icon + suffix + ".svgz"
274 << ":/classic/images/classic/" + icon + suffix + ".png";
275 }
276 iconNames << ":/symbols-ng/icons/" + icon + suffix + ".svg" ;//voruebergehend
277 iconNames << ":/symbols-ng/icons/" + icon + suffix + ".png"; //voruebergehend
278 iconNames << ":/images/" + icon + ".png";
279 }
280
281 foreach (const QString &name, iconNames) {
282 if (QFileInfo::exists(name))
283 return name;
284 }
285
286 return icon;
287 }
288
289 /*!
290 * \brief search icon
291 *
292 * Looks up icon by name. If settings allow, tries to use system icons, otherwise tries to find in txs resources.
293 * \param icon icon name
294 * \return icon
295 */
getRealIcon(const QString & icon)296 QIcon getRealIcon(const QString &icon)
297 {
298 if (icon.isEmpty()) return QIcon();
299 if (icon.startsWith(":/")) return QIcon(icon);
300 if (useSystemTheme && QIcon::hasThemeIcon(icon)) return QIcon::fromTheme(icon);
301 //return QIcon(getRealIconFile(icon.contains(".")?icon:(icon+".png")));
302 QString name = getRealIconFile(icon);
303 QIcon ic = QIcon(name);
304 //if(ic.isNull()){
305 #if (defined(Q_OS_OSX))
306 QPixmap pm(32, 32);
307 pm.load(name);
308 ic = QIcon(pm);
309 #endif
310 return ic;
311 }
312
313 /*!
314 * \brief search icon
315 *
316 * Looks up icon by name. If settings allow, tries to use system icons, otherwise tries to find in txs resources.
317 * The icon is cached for better reactivity.
318 * \param icon icon name
319 * \param forceReload ignore cache and reload icon from database. This is necessary if light-/dark-mode is changed.
320 * \return icon
321 */
getRealIconCached(const QString & icon,bool forceReload)322 QIcon getRealIconCached(const QString &icon, bool forceReload)
323 {
324 if (iconCache.contains(icon) && !forceReload) {
325 return *iconCache[icon];
326 }
327 if (icon.isEmpty()) return QIcon();
328
329 if (icon.startsWith(":/")) {
330 QIcon *icn = new QIcon(icon);
331 iconCache.insert(icon, icn);
332 return *icn;
333 }
334 if (useSystemTheme && QIcon::hasThemeIcon(icon)) {
335 QIcon *icn = new QIcon(QIcon::fromTheme(icon));
336 iconCache.insert(icon, icn);
337 return *icn;
338 }
339
340 //return QIcon(getRealIconFile(icon.contains(".")?icon:(icon+".png")));
341 QIcon *icn = new QIcon(getRealIconFile(icon));
342 iconCache.insert(icon, icn);
343 return *icn;
344 }
345
346 /*!
347 * \brief Tries to determine if the system uses dark mode
348 *
349 * This function tries to determine if a system uses "dark mode" by looking up general text color and converting it into gray-scale value.
350 * A value above 200 (scale is 0 .. 255 ) is considered as light text color on probably dark background, hence a dark mode is detected.
351 * This approach is independent on specific on different systems.
352 * \return true -> uses dark mode
353 */
systemUsesDarkMode(const QPalette & pal)354 bool systemUsesDarkMode(const QPalette &pal)
355 {
356 QColor clr=pal.color(QPalette::Text);
357 return qGray(clr.rgb())>200;
358 }
359
isFileRealWritable(const QString & filename)360 bool isFileRealWritable(const QString &filename)
361 {
362 if(QFileInfo::exists(filename)){
363 return QFileInfo(filename).isWritable();
364 }
365
366 QFile fi(filename);
367 bool result = false;
368 if (fi.exists()) result = fi.open(QIODevice::ReadWrite);
369 else {
370 result = fi.open(QIODevice::WriteOnly);
371 fi.remove();
372 }
373 return result;
374 }
375 /*!
376 * \brief checks if file exists and is writeable
377 *
378 * Convenience function
379 * \param filename
380 * \return
381 */
isExistingFileRealWritable(const QString & filename)382 bool isExistingFileRealWritable(const QString &filename)
383 {
384 return QFileInfo::exists(filename) && isFileRealWritable(filename);
385 }
386
387 /*!
388 * \brief make sure that the dirPath is terminated with a separator (/ or \)
389 *
390 * \param dirPath
391 * \return dirPath with trailing separator
392 */
393
ensureTrailingDirSeparator(const QString & dirPath)394 QString ensureTrailingDirSeparator(const QString &dirPath)
395 {
396 if (dirPath.isEmpty() || dirPath.endsWith("/")) return dirPath;
397 if (dirPath.endsWith(QDir::separator())) return dirPath;
398 #ifdef Q_OS_WIN32
399 if (dirPath.endsWith("\\")) return dirPath; //you can create a directory named \ on linux
400 #endif
401 return dirPath + "/";
402 }
403 /*!
404 * \brief join dirname and filename with apropriate separator
405 * \param dirname
406 * \param filename
407 * \return
408 */
joinPath(const QString & dirname,const QString & filename)409 QString joinPath(const QString &dirname, const QString &filename)
410 {
411 return ensureTrailingDirSeparator(dirname) + filename;
412 }
413 /*!
414 * \brief join dirname, dirname2 and filename with apropriate separator
415 * \param dirname
416 * \param dirname2
417 * \param filename
418 * \return
419 */
joinPath(const QString & dirname,const QString & dirname2,const QString & filename)420 QString joinPath(const QString &dirname, const QString &dirname2, const QString &filename)
421 {
422 return ensureTrailingDirSeparator(dirname) + ensureTrailingDirSeparator(dirname2) + filename;
423 }
424
425 /// Removes any symbolic link inside the file path.
426 /// Does nothing on Windows.
getNonSymbolicFileInfo(const QFileInfo & info)427 QFileInfo getNonSymbolicFileInfo(const QFileInfo& info)
428 {
429 #ifdef Q_OS_UNIX
430 const size_t MAX_DIR_DEPTH=32; //< Do not seek for symbolic links deeper than MAX_DIR_DEPTH.
431 // For performance issues and if the root directory was not catched (infinite loop).
432 // Static array might be also used to prevent heap allocation for a small amont of data. QFileInfo is shared, so the size of the array is size_of(void*)*MAX_DIR_DEPTH
433 //QFileInfo stack[MAX_DIR_DEPTH];
434 QStack<QFileInfo> stack;
435 stack.reserve(MAX_DIR_DEPTH);
436 stack.push(info);
437 size_t depth = 0;
438 int lastChanged = 0;
439
440 QFileInfo pfi ;
441 do
442 {
443 QDir parent = stack.top().dir();
444 pfi = QFileInfo(parent.absolutePath());
445 if (pfi.isSymLink())
446 {
447 pfi = QFileInfo(pfi.symLinkTarget());
448 lastChanged = depth; // = stack.size()-1;
449 }
450 stack.push(pfi);
451 depth++;
452 } while(!pfi.isRoot() && depth < MAX_DIR_DEPTH);
453
454 //if (Q_UNLIKELY(lastChanged != -1))
455 //{
456 pfi = stack[lastChanged];
457 int i = lastChanged -1;
458 for(; i>= 0; i-- ){
459 QFileInfo& ci = stack[i];
460 pfi = QFileInfo( QDir(pfi.absoluteFilePath()), ci.fileName() );
461 }
462 return pfi;
463 //}
464
465 #else
466 // Does nothing on Windows
467 return info;
468 #endif
469 }
470 /*!
471 * \brief replace file extension
472 * \param filename
473 * \param newExtension
474 * \param appendIfNoExt
475 * \return
476 */
replaceFileExtension(const QString & filename,const QString & newExtension,bool appendIfNoExt)477 QString replaceFileExtension(const QString &filename, const QString &newExtension, bool appendIfNoExt)
478 {
479 QFileInfo fi(filename);
480 QString ext = newExtension.startsWith('.') ? newExtension.mid(1) : newExtension;
481 if (fi.suffix().isEmpty()) {
482 if (appendIfNoExt)
483 return filename + '.' + ext;
484 else
485 return QString();
486 }
487 // exchange the suffix explicitly instead of using fi.completeBaseName()
488 // so that the filename stays exactly the same
489 return filename.left(filename.length() - fi.suffix().length()) + ext;
490 }
491
getRelativeBaseNameToPath(const QString & file,QString basepath,bool baseFile,bool keepSuffix)492 QString getRelativeBaseNameToPath(const QString &file, QString basepath, bool baseFile, bool keepSuffix)
493 {
494 basepath.replace(QDir::separator(), "/");
495 if (basepath.endsWith("/")) basepath = basepath.left(basepath.length() - 1);
496
497 QFileInfo fi(file);
498 QString filename = fi.fileName();
499 QString path = fi.path();
500 if (path.endsWith("/")) path = path.left(path.length() - 1);
501 QStringList basedirs = basepath.split("/");
502 if (baseFile && !basedirs.isEmpty()) basedirs.removeLast();
503 QStringList dirs = path.split("/");
504 //QStringList basedirs = QStringList::split("/", basepath, false);
505 //QStringList dirs = QStringList::split("/", path, false);
506
507 int nDirs = dirs.count();
508
509 while (dirs.count() > 0 && basedirs.count() > 0 && dirs[0] == basedirs[0]) {
510 dirs.pop_front();
511 basedirs.pop_front();
512 }
513
514 if (nDirs != dirs.count()) {
515 path = dirs.join("/");
516
517 if (basedirs.count() > 0) {
518 for (int j = 0; j < basedirs.count(); ++j) {
519 path = "../" + path;
520 }
521 }
522
523 //if (path.length()>0 && path.right(1) != "/") path = path + "/";
524 } else {
525 path = fi.path();
526 }
527
528 if (path.length() > 0 && !path.endsWith("/") && !path.endsWith("\\")) path += "/"; //necessary if basepath isn't given
529
530 if (keepSuffix)
531 return path + filename;
532 return path + fi.completeBaseName();
533 }
534
getPathfromFilename(const QString & compFile)535 QString getPathfromFilename(const QString &compFile)
536 {
537 if (compFile.isEmpty()) return "";
538 QString dir = QFileInfo(compFile).absolutePath();
539 if (!dir.endsWith("/") && !dir.endsWith(QDir::separator())) dir.append(QDir::separator());
540 return dir;
541 }
542
findAbsoluteFilePath(const QString & relName,const QString & extension,const QStringList & searchPaths,const QString & fallbackPath)543 QString findAbsoluteFilePath(const QString &relName, const QString &extension, const QStringList &searchPaths, const QString &fallbackPath)
544 {
545 QString s = relName;
546 QString ext = extension;
547 if (!ext.isEmpty() && !ext.startsWith(".")) ext = "." + ext;
548 if (!s.endsWith(ext, Qt::CaseInsensitive)) s += ext;
549 QFileInfo fi(s);
550 if (!fi.isRelative()) return s;
551 foreach (const QString &path, searchPaths) {
552 fi.setFile(QDir(path), s);
553 if (fi.exists()) return fi.absoluteFilePath();
554 }
555 QString fbp = fallbackPath;
556 if (!fbp.isEmpty() && !fbp.endsWith('/') && !fbp.endsWith(QDir::separator())) fbp += QDir::separator();
557 return fbp + s; // fallback
558 }
559
560 /*!
561 * Tries to get a non-existent filename. If guess, does not exist, return it.
562 * Otherwise, try find a non-extistent filename by increasing a number at the end
563 * of the filesname. If there is already a number, start from there, e.g.
564 * test02.txt -> test03.txt. If no free filename could be determined, return fallback.
565 */
getNonextistentFilename(const QString & guess,const QString & fallback)566 QString getNonextistentFilename(const QString &guess, const QString &fallback)
567 {
568 QFileInfo fi(guess);
569 if (!fi.exists()) return guess;
570 QRegExp reNumberedFilename("(.*[^\\d])(\\d*)\\.(\\w+)");
571 if (!reNumberedFilename.exactMatch(guess)) {
572 return fallback;
573 }
574 QString base = reNumberedFilename.cap(1);
575 QString ext = reNumberedFilename.cap(3);
576 int num = reNumberedFilename.cap(2).toInt();
577 int numLen = reNumberedFilename.cap(2).length();
578
579 for (int i = num + 1; i <= 1000000; i++) {
580 QString filename = QString("%1%2.%3").arg(base).arg(i, numLen, 10, QLatin1Char('0')).arg(ext);
581 fi.setFile(filename);
582 if (!fi.exists())
583 return filename;
584 }
585 return fallback;
586 }
587 /*!
588 * \brief get environment path
589 *
590 * Get the content of PATH environment variable.
591 * On OSX starts a bash to get a more complete picture as programs get started without access to the bash PATH.
592 * \return path
593 */
getEnvironmentPath()594 QString getEnvironmentPath()
595 {
596 static QString path;
597 if (path.isNull()) {
598 #ifdef Q_OS_MAC
599 QProcess *myProcess = new QProcess();
600 myProcess->start("/usr/libexec/path_helper"); // -n ensures there is no newline at the end
601
602 myProcess->waitForFinished(3000);
603 if (myProcess->exitStatus() == QProcess::NormalExit) {
604 QByteArray res = myProcess->readAllStandardOutput();
605 path = QString(res).split('\"').at(1); // bash may have some initial output. path is on the last line
606 } else {
607 path = "";
608 }
609 delete myProcess;
610 #else
611 path = QProcessEnvironment::systemEnvironment().value("PATH");
612 #endif
613 }
614 return path;
615 }
616 /*!
617 * \brief get environment path list
618 * Same as getEnvironmentPath(), but splits the sresult into a stringlist.
619 * \return
620 */
getEnvironmentPathList()621 QStringList getEnvironmentPathList()
622 {
623 return getEnvironmentPath().split(getPathListSeparator());
624 }
625 /*!
626 * \brief update path settings for a process
627 * \param proc process
628 * \param additionalPaths
629 */
updatePathSettings(QProcess * proc,QString additionalPaths)630 void updatePathSettings(QProcess *proc, QString additionalPaths)
631 {
632 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
633 QString path(getEnvironmentPath());
634 if (!additionalPaths.isEmpty()) {
635 path += getPathListSeparator() + additionalPaths;
636 }
637 env.insert("PATH", path);
638 // Note: this modifies the path only for the context of the called program. It does not affect the search path for the program itself.
639 proc->setProcessEnvironment(env);
640 }
641
showInGraphicalShell(QWidget * parent,const QString & pathIn)642 void showInGraphicalShell(QWidget *parent, const QString &pathIn)
643 {
644 // Mac, Windows support folder or file.
645 #if defined(Q_OS_WIN)
646 QFileInfo fiExplorer(QProcessEnvironment::systemEnvironment().value("WINDIR"), "explorer.exe");
647 if (!fiExplorer.exists()) {
648 QMessageBox::warning(parent,
649 QApplication::translate("Texstudio",
650 "Launching Windows Explorer Failed"),
651 QApplication::translate("Texstudio",
652 "Could not find explorer.exe in path to launch Windows Explorer."));
653 return;
654 }
655 QStringList param;
656 if (!QFileInfo(pathIn).isDir())
657 param += QLatin1String("/select,");
658 param += QDir::toNativeSeparators(pathIn);
659 QProcess::startDetached(fiExplorer.absoluteFilePath(), param);
660 #elif defined(Q_OS_MAC)
661 QStringList scriptArgs;
662 scriptArgs << QLatin1String("-e")
663 << QString::fromLatin1("tell application \"Finder\" to reveal POSIX file \"%1\"")
664 .arg(pathIn);
665 QProcess::execute(QLatin1String("/usr/bin/osascript"), scriptArgs);
666 scriptArgs.clear();
667 scriptArgs << QLatin1String("-e")
668 << QLatin1String("tell application \"Finder\" to activate");
669 QProcess::execute(QLatin1String("/usr/bin/osascript"), scriptArgs);
670 #else
671 // we cannot select a file here, because no file browser really supports it...
672 using namespace Utils;
673 const QFileInfo fileInfo(pathIn);
674 const QString folder = fileInfo.isDir() ? fileInfo.absoluteFilePath() : fileInfo.filePath();
675 QSettings dummySettings;
676 const QString app = UnixUtils::fileBrowser(&dummySettings);
677 QProcess browserProc;
678 const QString browserArg = UnixUtils::substituteFileBrowserParameters(app, folder);
679 #if QT_VERSION>=QT_VERSION_CHECK(5,15,0)
680 QStringList args=QProcess::splitCommand(browserArg);
681 #else
682 QStringList args=browserArg.split(" "); // this assumes that the command is not using quotes with spaces in the command path, better solution from qt5.15 ...
683 #endif
684 if(args.isEmpty())
685 return;
686 QString cmd=args.takeFirst();
687 for(QString &elem:args){
688 elem=removeQuote(elem);
689 }
690 bool success = browserProc.startDetached(cmd,args);
691 const QString error = QString::fromLocal8Bit(browserProc.readAllStandardError());
692 success = success && error.isEmpty();
693 if (!success)
694 QMessageBox::critical(parent, app, error);
695 #endif
696 }
697
msgGraphicalShellAction()698 QString msgGraphicalShellAction()
699 {
700 #if defined(Q_OS_WIN)
701 return QApplication::translate("Texstudio", "Show in Explorer");
702 #elif defined(Q_OS_MAC)
703 return QApplication::translate("Texstudio", "Show in Finder");
704 #else
705 return QApplication::translate("Texstudio", "Show Containing Folder");
706 #endif
707 }
708 /*!
709 * \brief determine which x11 environment is used.
710 * This is probably obsolete.
711 * \return 0 : no kde ; 3: kde ; 4 : kde4 ; 5 : kde5
712 */
x11desktop_env()713 int x11desktop_env()
714 {
715 // 0 : no kde ; 3: kde ; 4 : kde4 ; 5 : kde5 ;
716 QString kdesession = ::getenv("KDE_FULL_SESSION");
717 QString kdeversion = ::getenv("KDE_SESSION_VERSION");
718 if (!kdeversion.isEmpty()) {
719 return kdeversion.toInt();
720 }
721 if (!kdesession.isEmpty()) return 3;
722 return 0;
723 }
724
725 // detect a retina macbook via the model identifier
726 // http://support.apple.com/kb/HT4132?viewlocale=en_US&locale=en_US
isRetinaMac()727 bool isRetinaMac()
728 {
729 #ifdef Q_OS_MAC
730 static bool firstCall = true;
731 static bool isRetina = false;
732 if (firstCall) {
733 firstCall = false;
734 QProcess process;
735 process.start("sysctl", QStringList() << "-n" << "hw.model");
736 process.waitForFinished(1000);
737 QString model(process.readAllStandardOutput()); // is something like "MacBookPro10,1"
738 QRegExp rx("MacBookPro([0-9]*)");
739 rx.indexIn(model);
740 int num = rx.cap(1).toInt();
741 if (num >= 10) // compatibility with future MacBookPros. Assume they are also retina.
742 isRetina = true;
743 }
744 return isRetina;
745 #else
746 return false;
747 #endif
748 }
749
hasAtLeastQt(int major,int minor)750 bool hasAtLeastQt(int major, int minor)
751 {
752 QStringList vers = QString(qVersion()).split('.');
753 if (vers.count() < 2) return false;
754 int ma = vers[0].toInt();
755 int mi = vers[1].toInt();
756 return (ma > major) || (ma == major && mi >= minor);
757 }
758
759 /// convenience function for unique connections independent of the Qt version
connectUnique(const QObject * sender,const char * signal,const QObject * receiver,const char * method)760 bool connectUnique(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
761 {
762 return QObject::connect(sender, signal, receiver, method, Qt::UniqueConnection);
763 }
764
sleep(unsigned long s)765 void ThreadBreaker::sleep(unsigned long s)
766 {
767 QThread::sleep(s);
768 }
769
msleep(unsigned long ms)770 void ThreadBreaker::msleep(unsigned long ms)
771 {
772 QThread::msleep(ms);
773 };
774
forceTerminate(QThread * t)775 void ThreadBreaker::forceTerminate(QThread *t)
776 {
777 if (!t) t = QThread::currentThread();
778 t->setTerminationEnabled(true);
779 t->terminate();
780 }
781
SafeThread()782 SafeThread::SafeThread(): QThread(nullptr), crashed(false) {}
SafeThread(QObject * parent)783 SafeThread::SafeThread(QObject *parent): QThread(parent), crashed(false) {}
784
wait(unsigned long time)785 void SafeThread::wait(unsigned long time)
786 {
787 if (crashed) return;
788 QThread::wait(time);
789 }
790
791
convertStringListtoSet(const QStringList & list)792 QSet<QString> convertStringListtoSet(const QStringList &list)
793 {
794 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
795 return QSet<QString>(list.begin(),list.end());
796 #else
797 return QSet<QString>::fromList(list);
798 #endif
799
800 }
801