1 /*******************************************************************
2 
3 Part of the Fritzing project - http://fritzing.org
4 Copyright (c) 2007-2014 Fachhochschule Potsdam - http://fh-potsdam.de
5 
6 Fritzing 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 Fritzing 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 Fritzing.  If not, see <http://www.gnu.org/licenses/>.
18 
19 ********************************************************************
20 
21 $Revision: 7004 $:
22 $Author: irascibl@gmail.com $:
23 $Date: 2013-04-29 13:10:59 +0200 (Mo, 29. Apr 2013) $
24 
25 ********************************************************************/
26 
27 #include "folderutils.h"
28 #include "lockmanager.h"
29 #include "textutils.h"
30 #include <QDesktopServices>
31 #include <QCoreApplication>
32 #include <QSettings>
33 #include <QTextStream>
34 #include <QUuid>
35 #include <QCryptographicHash>
36 #include <QProcess>
37 #include <QDesktopServices>
38 #include <QUrl>
39 #include <QFileInfo>
40 
41 #include "../debugdialog.h"
42 #ifdef QUAZIP_INSTALLED
43     #include <quazip/quazip.h>
44     #include <quazip/quazipfile.h>
45 #else
46     #include "../lib/quazip/quazip.h"
47     #include "../lib/quazip/quazipfile.h"
48 #endif
49 #include "../lib/qtsysteminfo/QtSystemInfo.h"
50 
51 
52 FolderUtils* FolderUtils::singleton = NULL;
53 QString FolderUtils::m_openSaveFolder = "";
54 
FolderUtils()55 FolderUtils::FolderUtils() {
56 	m_openSaveFolder = ___emptyString___;
57 	m_folders
58 		<< "/bins"
59 		<< "/partfactory"
60 		<< "/parts/user" << "/parts/contrib"
61 		<< "/parts/svg/user/icon" << "/parts/svg/user/breadboard" << "/parts/svg/user/schematic" << "/parts/svg/user/pcb"
62 		<< "/parts/svg/contrib/icon" << "/parts/svg/contrib/breadboard" << "/parts/svg/contrib/schematic" << "/parts/svg/contrib/pcb"
63 		<< "/backup"
64 		<< "/fzz";
65 }
66 
~FolderUtils()67 FolderUtils::~FolderUtils() {
68 }
69 
70 // finds a subfolder of the application directory searching backward up the tree
getApplicationSubFolder(QString search)71 QDir * FolderUtils::getApplicationSubFolder(QString search) {
72 	if (singleton == NULL) {
73 		singleton = new FolderUtils();
74 	}
75 
76 	QString path = singleton->applicationDirPath();
77     path += "/" + search;
78 	//DebugDialog::debug(QString("path %1").arg(path) );
79     QDir* dir= new QDir(path);
80     while (!dir->exists()) {
81     	// if we're running from the debug or release folder, go up one to find things
82     	dir->cdUp();
83 		dir->cdUp();
84     	if (dir->isRoot()) return NULL;   // didn't find the search folder
85 
86 		dir->setPath(dir->absolutePath() + "/" + search);
87    	}
88 
89    	return dir;
90 }
91 
getApplicationSubFolderPath(QString search)92 QString FolderUtils::getApplicationSubFolderPath(QString search) {
93 	if (singleton == NULL) {
94 		singleton = new FolderUtils();
95 	}
96 
97 	QDir * dir = getApplicationSubFolder(search);
98 	if (dir == NULL) return "";
99 
100 	QString result = dir->path();
101 	delete dir;
102 	return result;
103 }
104 
getUserDataStorePath(QString folder)105 QString FolderUtils::getUserDataStorePath(QString folder) {
106 	QString settingsFile = QSettings(QSettings::IniFormat,QSettings::UserScope,"Fritzing","Fritzing").fileName();
107 	return QFileInfo(settingsFile).dir()
108         .absolutePath()+(folder.isEmpty()?"":QString("/")+folder);
109 }
110 
getUserDataStoreFolders()111 const QStringList & FolderUtils::getUserDataStoreFolders() {
112 	if (singleton == NULL) {
113 		singleton = new FolderUtils();
114 	}
115 
116 	return singleton->userDataStoreFolders();
117 }
118 
createFolderAnCdIntoIt(QDir & dir,QString newFolder)119 bool FolderUtils::createFolderAnCdIntoIt(QDir &dir, QString newFolder) {
120 	if(!dir.mkdir(newFolder)) return false;
121 	if(!dir.cd(newFolder)) return false;
122 
123 	return true;
124 }
125 
setApplicationPath(const QString & path)126 bool FolderUtils::setApplicationPath(const QString & path)
127 {
128 	if (singleton == NULL) {
129 		singleton = new FolderUtils();
130 	}
131 
132 	return singleton->setApplicationPath2(path);
133 }
134 
cleanup()135 void FolderUtils::cleanup() {
136 	if (singleton) {
137 		delete singleton;
138 		singleton = NULL;
139 	}
140 }
141 
getLibraryPath()142 const QString FolderUtils::getLibraryPath()
143 {
144 	if (singleton == NULL) {
145 		singleton = new FolderUtils();
146 	}
147 
148 	return singleton->libraryPath();
149 }
150 
151 
libraryPath()152 const QString FolderUtils::libraryPath()
153 {
154 #ifdef Q_OS_MAC
155 	// mac plugins are always in the bundle
156 	return QDir::cleanPath(QCoreApplication::applicationDirPath() + "/../lib");
157 #endif
158 
159 	return QDir::cleanPath(applicationDirPath() + "/lib");
160 }
161 
applicationDirPath()162 const QString FolderUtils::applicationDirPath() {
163 	if (m_appPath.isEmpty()) {
164 #ifdef Q_OS_WIN
165 		return QCoreApplication::applicationDirPath();
166 #else
167 		// look in standard Fritzing location (applicationDirPath and parent folders) then in standard linux locations
168 		QStringList candidates;
169 		candidates.append(QCoreApplication::applicationDirPath());
170 		QDir dir(QCoreApplication::applicationDirPath());
171 		if (dir.cdUp()) {
172 			candidates.append(dir.absolutePath());
173 			if (dir.cdUp()) {
174 				candidates.append(dir.absolutePath());
175 				if (dir.cdUp()) {
176 					candidates.append(dir.absolutePath());
177 				}
178 			}
179 		}
180 
181 #ifdef PKGDATADIR
182 		candidates.append(QLatin1String(PKGDATADIR));
183 #else
184 		candidates.append("/usr/share/fritzing");
185 		candidates.append("/usr/local/share/fritzing");
186 #endif
187 		candidates.append(QDir::homePath() + "/.local/share/fritzing");
188 		foreach (QString candidate, candidates) {
189             //DebugDialog::debug(QString("candidate:%1").arg(candidate));
190 			QDir dir(candidate);
191             if (!dir.exists("parts")) continue;
192 
193             if (dir.exists("bins")) {
194 				m_appPath = candidate;
195 				return m_appPath;
196 			}
197 
198 		}
199 
200 		m_appPath = QCoreApplication::applicationDirPath();
201         DebugDialog::debug("data folders not found");
202 
203 #endif
204 	}
205 
206 	return m_appPath;
207 }
208 
setApplicationPath2(const QString & path)209 bool FolderUtils::setApplicationPath2(const QString & path)
210 {
211 	QDir dir(path);
212 	if (!dir.exists()) return false;
213 
214 	m_appPath = path;
215 	return true;
216 }
217 
userDataStoreFolders()218 const QStringList & FolderUtils::userDataStoreFolders() {
219 	return m_folders;
220 }
221 
setOpenSaveFolder(const QString & path)222 void FolderUtils::setOpenSaveFolder(const QString& path) {
223 	setOpenSaveFolderAux(path);
224 	QSettings settings;
225 	settings.setValue("openSaveFolder", m_openSaveFolder);
226 }
227 
setOpenSaveFolderAux(const QString & path)228 void FolderUtils::setOpenSaveFolderAux(const QString& path)
229 {
230 	QFileInfo fileInfo(path);
231 	if(fileInfo.isDir()) {
232 		m_openSaveFolder = path;
233 	} else {
234 		m_openSaveFolder = fileInfo.path().remove(fileInfo.fileName());
235 	}
236 }
237 
238 
openSaveFolder()239 const QString FolderUtils::openSaveFolder() {
240 	if(m_openSaveFolder.isEmpty()) {
241 		QSettings settings;
242 		QString tempFolder = settings.value("openSaveFolder").toString();
243 		if (!tempFolder.isEmpty()) {
244 			QFileInfo fileInfo(tempFolder);
245 			if (fileInfo.exists()) {
246 				m_openSaveFolder = tempFolder;
247 				return m_openSaveFolder;
248 			}
249 			else {
250 				settings.remove("openSaveFolder");
251 			}
252 		}
253 
254 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
255 		DebugDialog::debug(QString("default save location: %1").arg(QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation)));
256 		return QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation);
257 #else
258 		DebugDialog::debug(QString("default save location: %1").arg(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)));
259 		return QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
260 #endif
261 	} else {
262 		return m_openSaveFolder;
263 	}
264 }
265 
266 
getOpenFileName(QWidget * parent,const QString & caption,const QString & dir,const QString & filter,QString * selectedFilter,QFileDialog::Options options)267 QString FolderUtils::getOpenFileName( QWidget * parent, const QString & caption, const QString & dir, const QString & filter, QString * selectedFilter, QFileDialog::Options options )
268 {
269 	QString result = QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options);
270 	if (!result.isNull()) {
271 		setOpenSaveFolder(result);
272 	}
273 	return result;
274 }
275 
getOpenFileNames(QWidget * parent,const QString & caption,const QString & dir,const QString & filter,QString * selectedFilter,QFileDialog::Options options)276 QStringList FolderUtils::getOpenFileNames( QWidget * parent, const QString & caption, const QString & dir, const QString & filter, QString * selectedFilter, QFileDialog::Options options )
277 {
278 	QStringList result = QFileDialog::getOpenFileNames(parent, caption, dir, filter, selectedFilter, options);
279 	if (result.count() > 0) {
280 		setOpenSaveFolder(result.at(0));
281 	}
282 	return result;
283 }
284 
getSaveFileName(QWidget * parent,const QString & caption,const QString & dir,const QString & filter,QString * selectedFilter,QFileDialog::Options options)285 QString FolderUtils::getSaveFileName( QWidget * parent, const QString & caption, const QString & dir, const QString & filter, QString * selectedFilter, QFileDialog::Options options )
286 {
287 	//DebugDialog::debug(QString("getopenfilename %1 %2 %3 %4").arg(caption).arg(dir).arg(filter).arg(*selectedFilter));
288 	QString result = QFileDialog::getSaveFileName(parent, caption, dir, filter, selectedFilter, options);
289 	if (!result.isNull()) {
290 		setOpenSaveFolder(result);
291 	}
292 	return result;
293 }
294 
isEmptyFileName(const QString & fileName,const QString & untitledFileName)295 bool FolderUtils::isEmptyFileName(const QString &fileName, const QString &untitledFileName) {
296 	return (fileName.isEmpty() || fileName.isNull() || fileName.startsWith(untitledFileName));
297 }
298 
replicateDir(QDir srcDir,QDir targDir)299 void FolderUtils::replicateDir(QDir srcDir, QDir targDir) {
300 	// copy all files from srcDir source to tagDir
301 	QStringList files = srcDir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot);
302 	for(int i=0; i < files.size(); i++) {
303 		QFile tempFile(srcDir.path() + "/" +files.at(i));
304 		DebugDialog::debug(QObject::tr("Copying file %1").arg(tempFile.fileName()));
305 		QFileInfo fi(files.at(i));
306 		QString newFilePath = targDir.path() + "/" + fi.fileName();
307 		if(QFileInfo(tempFile.fileName()).isDir()) {
308 			QDir newTargDir = QDir(newFilePath);
309 			newTargDir.mkpath(newTargDir.absolutePath());
310 			newTargDir.cd(files.at(i));
311 			replicateDir(QDir(tempFile.fileName()),newTargDir);
312 		} else {
313 			if(!tempFile.copy(newFilePath)) {
314 				DebugDialog::debug(QObject::tr("File %1 already exists: it won't be overwritten").arg(newFilePath));
315 			}
316 		}
317 	}
318 }
319 
320 // NOTE: This function cannot remove directories that have non-empty name filters set on it.
rmdir(const QString & dirPath)321 void FolderUtils::rmdir(const QString &dirPath) {
322 	QDir dir = QDir(dirPath);
323 	rmdir(dir);
324 }
325 
rmdir(QDir & dir)326 void FolderUtils::rmdir(QDir & dir) {
327 	//DebugDialog::debug(QString("removing folder: %1").arg(dir.path()));
328 
329 	QStringList files = dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot);
330 	for(int i=0; i < files.size(); i++) {
331 		QFile tempFile(dir.path() + "/" +files.at(i));
332 		//DebugDialog::debug(QString("removing from original folder: %1").arg(tempFile.fileName()));
333 		if(QFileInfo(tempFile.fileName()).isDir()) {
334 			QDir dir = QDir(tempFile.fileName());
335 			rmdir(dir);
336 		} else {
337 			tempFile.remove(tempFile.fileName());
338 		}
339 	}
340 	dir.rmdir(dir.path());
341 }
342 
createZipAndSaveTo(const QDir & dirToCompress,const QString & filepath,const QStringList & skipSuffixes)343 bool FolderUtils::createZipAndSaveTo(const QDir &dirToCompress, const QString &filepath, const QStringList & skipSuffixes) {
344 	DebugDialog::debug("zipping "+dirToCompress.path()+" into "+filepath);
345 
346 	QString tempZipFile = QDir::temp().path()+"/"+TextUtils::getRandText()+".zip";
347 	DebugDialog::debug("temp file: "+tempZipFile);
348 	QuaZip zip(tempZipFile);
349 	if(!zip.open(QuaZip::mdCreate)) {
350 		qWarning("zip.open(): %d", zip.getZipError());
351 		return false;
352 	}
353 
354 	QFileInfoList files=dirToCompress.entryInfoList();
355 	QFile inFile;
356 	QuaZipFile outFile(&zip);
357 	char c;
358 
359 	QString currFolderBU = QDir::currentPath();
360 	QDir::setCurrent(dirToCompress.path());
361 	foreach(QFileInfo file, files) {
362 		if(!file.isFile()||file.fileName()==filepath) continue;
363 		if (file.fileName().contains(LockManager::LockedFileName)) continue;
364 
365         bool skip = false;
366         foreach (QString suffix, skipSuffixes) {
367             if (file.fileName().endsWith(suffix)) {
368                 skip = true;
369                 break;
370             }
371         }
372         if (skip) continue;
373 
374 //#pragma message("remove fzz check")
375 //if (file.fileName().endsWith(".fzz")) continue;
376 
377 		inFile.setFileName(file.fileName());
378 
379 		if(!inFile.open(QIODevice::ReadOnly)) {
380 			qWarning("inFile.open(): %s", inFile.errorString().toLocal8Bit().constData());
381 			return false;
382 		}
383 		if(!outFile.open(QIODevice::WriteOnly, QuaZipNewInfo(inFile.fileName(), inFile.fileName()))) {
384 			qWarning("outFile.open(): %d", outFile.getZipError());
385 			return false;
386 		}
387 
388 		while(inFile.getChar(&c)&&outFile.putChar(c)){}
389 
390 		if(outFile.getZipError()!=UNZ_OK) {
391 			qWarning("outFile.putChar(): %d", outFile.getZipError());
392 			return false;
393 		}
394 		outFile.close();
395 		if(outFile.getZipError()!=UNZ_OK) {
396 			qWarning("outFile.close(): %d", outFile.getZipError());
397 			return false;
398 		}
399 		inFile.close();
400 	}
401 	zip.close();
402 	QDir::setCurrent(currFolderBU);
403 
404 	if(QFileInfo(filepath).exists()) {
405 		// if we're here the usr has already accepted to overwrite
406 		QFile::remove(filepath);
407 	}
408 	QFile file(tempZipFile);
409 	FolderUtils::slamCopy(file, filepath);
410 	file.remove();
411 
412 	if(zip.getZipError()!=0) {
413 		qWarning("zip.close(): %d", zip.getZipError());
414 		return false;
415 	}
416 	return true;
417 }
418 
419 
unzipTo(const QString & filepath,const QString & dirToDecompress,QString & error)420 bool FolderUtils::unzipTo(const QString &filepath, const QString &dirToDecompress, QString & error) {
421     static QChar badCharacters[] = { '\\', '/', ':', '*', '?', '"', '<', '>', '|' };
422     static QChar underscore('_');
423 
424 	QuaZip zip(filepath);
425 	if(!zip.open(QuaZip::mdUnzip)) {
426         error = QString("zip.open(): %d").arg(zip.getZipError());
427 		DebugDialog::debug(error);
428 		return false;
429 	}
430 
431 	zip.setFileNameCodec("IBM866");
432 	DebugDialog::debug(QString("unzipping %1 entries from %2").arg(zip.getEntriesCount()).arg(filepath));
433 	QuaZipFileInfo info;
434 	QuaZipFile file(&zip);
435 	QFile out;
436 	QString name;
437 	char c;
438 	for(bool more=zip.goToFirstFile(); more; more=zip.goToNextFile()) {
439 		if(!zip.getCurrentFileInfo(&info)) {
440 			error = QString("getCurrentFileInfo(): %d\n").arg(zip.getZipError());
441 			DebugDialog::debug(error);
442 			return false;
443 		}
444 
445 		if(!file.open(QIODevice::ReadOnly)) {
446 			error = QString("file.open(): %d").arg(file.getZipError());
447 			DebugDialog::debug(error);
448 			return false;
449 		}
450 		name=file.getActualFileName();
451 		if(file.getZipError()!=UNZ_OK) {
452 			error = QString("file.getFileName(): %d").arg(file.getZipError());
453 			DebugDialog::debug(error);
454 			return false;
455 		}
456 
457 		out.setFileName(dirToDecompress+"/"+name);
458 		// this will fail if "name" contains subdirectories, but we don't mind that
459 		if(!out.open(QIODevice::WriteOnly)) {
460             for (int i = 0; i < name.length(); i++) {
461                 if (name[i].unicode() < 32) {
462                     name.replace(i, 1, &underscore, 1);
463                 }
464                 else for (unsigned int j = 0; j < (sizeof(badCharacters) / sizeof(QChar)); j++) {
465                     if (name[i] == badCharacters[j]) {
466                         name.replace(i, 1, &underscore, 1);
467                         break;
468                     }
469                 }
470             }
471             out.setFileName(dirToDecompress+"/"+name);
472             if(!out.open(QIODevice::WriteOnly)) {
473                 error = QString("out.open(): %s").arg(out.errorString().toLocal8Bit().constData());
474 			    DebugDialog::debug(error);
475 			    return false;
476             }
477 		}
478 
479 		// Slow like hell (on GNU/Linux at least), but it is not my fault.
480 		// Not ZIP/UNZIP package's fault either.
481 		// The slowest thing here is out.putChar(c).
482 		// TODO: now that out.putChar has been replaced with a buffered write, is it still slow under Linux?
483 
484 #define BUFFERSIZE 1024
485 		char buffer[BUFFERSIZE];
486 		int ix = 0;
487 		while(file.getChar(&c)) {
488 			buffer[ix++] = c;
489 			if (ix == BUFFERSIZE) {
490 				out.write(buffer, ix);
491 				ix = 0;
492 			}
493 		}
494 		if (ix > 0) {
495 			out.write(buffer, ix);
496 		}
497 
498 		out.close();
499 		if(file.getZipError()!=UNZ_OK) {
500 			error = QString("file.getFileName(): %d").arg(file.getZipError());
501 			DebugDialog::debug(error);
502 			return false;
503 		}
504 		if(!file.atEnd()) {
505 			error = "read all but not EOF";
506 			DebugDialog::debug(error);
507 			return false;
508 		}
509 		file.close();
510 		if(file.getZipError()!=UNZ_OK) {
511 			error = QString("file.close(): %d").arg(file.getZipError());
512 			DebugDialog::debug(error);
513 			return false;
514 		}
515 	}
516 	zip.close();
517 	if(zip.getZipError()!=UNZ_OK) {
518 		error = QString("zip.close(): %d").arg(zip.getZipError());
519 		DebugDialog::debug(error);
520 		return false;
521 	}
522 	return true;
523 }
524 
collectFiles(const QDir & parent,QStringList & filters,QStringList & files,bool recursive)525 void FolderUtils::collectFiles(const QDir & parent, QStringList & filters, QStringList & files, bool recursive)
526 {
527 	QFileInfoList fileInfoList = parent.entryInfoList(filters, QDir::Files | QDir::Hidden | QDir::NoSymLinks);
528 	foreach (QFileInfo fileInfo, fileInfoList) {
529 		files.append(fileInfo.absoluteFilePath());
530 	}
531 
532     if (recursive) {
533         QFileInfoList dirList = parent.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::NoSymLinks);
534         foreach (QFileInfo dirInfo, dirList) {
535             QDir dir(dirInfo.filePath());
536             //DebugDialog::debug(QString("looking in backup dir %1").arg(dir.absolutePath()));
537 
538             collectFiles(dir, filters, files, recursive);
539         }
540     }
541 }
542 
543 
544 
makePartFolderHierarchy(const QString & prefixFolder,const QString & destFolder)545 void FolderUtils::makePartFolderHierarchy(const QString & prefixFolder, const QString & destFolder) {
546 	QDir dir(prefixFolder);
547 
548 	dir.mkdir(destFolder);
549 	dir.mkdir("svg");
550 	dir.cd("svg");
551 	dir.mkdir(destFolder);
552 	dir.cd(destFolder);
553 	dir.mkdir("icon");
554 	dir.mkdir("breadboard");
555 	dir.mkdir("schematic");
556 	dir.mkdir("pcb");
557 }
558 
copyBin(const QString & dest,const QString & source)559 void FolderUtils::copyBin(const QString & dest, const QString & source) {
560     if(QFileInfo(dest).exists()) return;
561 
562     // this copy action, is not working on windows, because is a resources file
563     if(!QFile(source).copy(dest)) {
564 #ifdef Q_OS_WIN // may not be needed from qt 4.5.2 on
565         DebugDialog::debug("Failed to copy a file from the resources");
566         QDir binsFolder = QFileInfo(dest).dir().absolutePath();
567         QStringList binFiles = binsFolder.entryList(QDir::AllEntries | QDir::NoDotAndDotDot);
568         foreach(QString binName, binFiles) {
569             if(binName.startsWith("qt_temp.")) {
570                 QString filePath = binsFolder.absoluteFilePath(binName);
571                 bool success = QFile(filePath).rename(dest);
572                 Q_UNUSED(success);
573                 break;
574             }
575         }
576 #endif
577     }
578     QFlags<QFile::Permission> ps = QFile::permissions(dest);
579     QFile::setPermissions(
580         dest,
581         QFile::WriteOwner | QFile::WriteUser | ps
582 #ifdef Q_OS_WIN
583         | QFile::WriteOther | QFile::WriteGroup
584 #endif
585 
586     );
587 }
588 
slamCopy(QFile & file,const QString & dest)589 bool FolderUtils::slamCopy(QFile & file, const QString & dest) {
590     QFileInfo info(file);
591     if (info.absoluteFilePath() == dest) {
592         // source = dest
593         return true;
594     }
595 
596     bool result = file.copy(dest);
597     if (result) return result;
598 
599     file.remove(dest);
600     return file.copy(dest);
601 }
602 
showInFolder(const QString & path)603 void FolderUtils::showInFolder(const QString & path)
604 {
605     // http://stackoverflow.com/questions/3490336/how-to-reveal-in-finder-or-show-in-explorer-with-qt
606     // http://stackoverflow.com/questions/9581330/change-selection-in-explorer-window
607     // Mac, Windows support folder or file.
608 #if defined(Q_OS_WIN)
609     const QString explorer = "explorer.exe";
610     QString param = QLatin1String("/e,/select,");
611     param += QDir::toNativeSeparators(path);
612     QProcess::startDetached(explorer, QStringList(param));
613 #elif defined(Q_OS_MAC)
614     QStringList scriptArgs;
615     scriptArgs << QLatin1String("-e")
616                << QString::fromLatin1("tell application \"Finder\" to reveal POSIX file \"%1\"")
617                                      .arg(path);
618     QProcess::execute(QLatin1String("/usr/bin/osascript"), scriptArgs);
619     scriptArgs.clear();
620     scriptArgs << QLatin1String("-e")
621                << QLatin1String("tell application \"Finder\" to activate");
622     QProcess::execute("/usr/bin/osascript", scriptArgs);
623 #else
624     QDesktopServices::openUrl( QUrl::fromLocalFile( QFileInfo(path).absolutePath() ) );
625 #endif
626 }
627