empty folders on MPD side (crash inserting/removing to/from model) usage of sys/time.h changed to QTime no unistd.h in Visual Studio if QT was build without SSL support, there is no QSslSocket class (at least in 5.6 and 5.8) missing strncasecmp in Visual Studio utime.sys is actually sys/utime.h in Visual Studio
980 lines
27 KiB
C++
980 lines
27 KiB
C++
/*
|
|
* Cantata
|
|
*
|
|
* Copyright (c) 2011-2017 Craig Drummond <craig.p.drummond@gmail.com>
|
|
*
|
|
* ----
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "utils.h"
|
|
#include "config.h"
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QDir>
|
|
#include <QProcess>
|
|
#include <QApplication>
|
|
#include <QDateTime>
|
|
#include <QTime>
|
|
#include <QWidget>
|
|
#include <QStyle>
|
|
#include <QDesktopWidget>
|
|
#include <QEventLoop>
|
|
#include <QStandardPaths>
|
|
#ifndef _MSC_VER
|
|
#include <unistd.h>
|
|
#include <utime.h>
|
|
#else
|
|
#include <sys/utime.h>
|
|
#endif
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#ifndef Q_OS_WIN
|
|
#include <grp.h>
|
|
#include <pwd.h>
|
|
#endif
|
|
#include <sys/types.h>
|
|
|
|
const QLatin1Char Utils::constDirSep('/');
|
|
const QLatin1String Utils::constDirSepStr("/");
|
|
const char * Utils::constDirSepCharStr="/";
|
|
|
|
static const QLatin1String constHttp("http://");
|
|
|
|
QString Utils::fixPath(const QString &dir, bool ensureEndsInSlash)
|
|
{
|
|
QString d(dir);
|
|
|
|
if (!d.isEmpty() && !d.startsWith(constHttp)) {
|
|
#ifdef Q_OS_WIN
|
|
// Windows shares can be \\host\share (which gets converted to //host/share)
|
|
// so if th epath starts with // we need to keep this double slash.
|
|
bool startsWithDoubleSlash=d.length()>2 && d.startsWith(QLatin1String("//"));
|
|
#endif
|
|
d.replace(QLatin1String("//"), constDirSepStr);
|
|
#ifdef Q_OS_WIN
|
|
if (startsWithDoubleSlash) { // Re add first slash
|
|
d=QLatin1Char('/')+d;
|
|
}
|
|
#endif
|
|
}
|
|
d.replace(QLatin1String("/./"), constDirSepStr);
|
|
if (ensureEndsInSlash && !d.isEmpty() && !d.endsWith(constDirSep)) {
|
|
d+=constDirSep;
|
|
}
|
|
return d;
|
|
}
|
|
|
|
#ifndef Q_OS_WIN
|
|
static const QLatin1String constTilda("~");
|
|
QString Utils::homeToTilda(const QString &s)
|
|
{
|
|
QString hp=QDir::homePath();
|
|
if (s==hp) {
|
|
return constTilda;
|
|
}
|
|
if (s.startsWith(hp+constDirSepStr)) {
|
|
return constTilda+fixPath(s.mid(hp.length()), false);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
QString Utils::tildaToHome(const QString &s)
|
|
{
|
|
if (s==constTilda) {
|
|
return fixPath(QDir::homePath());
|
|
}
|
|
if (s.startsWith(constTilda+constDirSep)) {
|
|
return fixPath(QDir::homePath()+constDirSepStr+s.mid(1), false);
|
|
}
|
|
return s;
|
|
}
|
|
#endif
|
|
|
|
QString Utils::getDir(const QString &file)
|
|
{
|
|
bool isCueFile=file.contains("/cue:///") && file.contains("?pos=");
|
|
QString d(file);
|
|
int slashPos(d.lastIndexOf(constDirSep));
|
|
|
|
if(slashPos!=-1) {
|
|
d.remove(slashPos+1, d.length());
|
|
}
|
|
|
|
if (isCueFile) {
|
|
d.remove("cue:///");
|
|
}
|
|
return fixPath(d);
|
|
}
|
|
|
|
QString Utils::getFile(const QString &file)
|
|
{
|
|
QString d(file);
|
|
int slashPos=d.lastIndexOf(constDirSep);
|
|
|
|
if (-1!=slashPos) {
|
|
d.remove(0, slashPos+1);
|
|
}
|
|
|
|
return d;
|
|
}
|
|
|
|
QString Utils::changeExtension(const QString &file, const QString &extension)
|
|
{
|
|
if (extension.isEmpty()) {
|
|
return file;
|
|
}
|
|
|
|
QString f(file);
|
|
int pos=f.lastIndexOf('.');
|
|
if (pos>1) {
|
|
f=f.left(pos+1);
|
|
}
|
|
|
|
if (f.endsWith('.')) {
|
|
return f+(extension.startsWith('.') ? extension.mid(1) : extension);
|
|
}
|
|
return f+(extension.startsWith('.') ? extension : (QChar('.')+extension));
|
|
}
|
|
|
|
bool Utils::isDirReadable(const QString &dir)
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
if (dir.isEmpty()) {
|
|
return false;
|
|
} else {
|
|
QDir d(dir);
|
|
bool dirReadable=d.isReadable();
|
|
// Handle cases where dir is set to \\server\ (i.e. no shared folder is set in path)
|
|
if (!dirReadable && dir.startsWith(QLatin1String("//")) && d.isRoot() && (dir.length()-1)==dir.indexOf(Utils::constDirSep, 2)) {
|
|
dirReadable=true;
|
|
}
|
|
return dirReadable;
|
|
}
|
|
#else
|
|
return dir.isEmpty() ? false : QDir(dir).isReadable();
|
|
#endif
|
|
}
|
|
|
|
QString Utils::strippedText(QString s)
|
|
{
|
|
s.remove(QString::fromLatin1("..."));
|
|
int i = 0;
|
|
while (i < s.size()) {
|
|
++i;
|
|
if (s.at(i - 1) != QLatin1Char('&')) {
|
|
continue;
|
|
}
|
|
|
|
if (i < s.size() && s.at(i) == QLatin1Char('&')) {
|
|
++i;
|
|
}
|
|
s.remove(i - 1, 1);
|
|
}
|
|
return s.trimmed();
|
|
}
|
|
|
|
QString Utils::stripAcceleratorMarkers(QString label)
|
|
{
|
|
int p = 0;
|
|
forever {
|
|
p = label.indexOf('&', p);
|
|
if(p < 0 || p + 1 >= label.length()) {
|
|
break;
|
|
}
|
|
|
|
if(label.at(p + 1).isLetterOrNumber() || label.at(p + 1) == '&') {
|
|
label.remove(p, 1);
|
|
}
|
|
|
|
++p;
|
|
}
|
|
return label;
|
|
}
|
|
|
|
QString Utils::convertPathForDisplay(const QString &path, bool isFolder)
|
|
{
|
|
if (path.isEmpty() || path.startsWith(constHttp)) {
|
|
return path;
|
|
}
|
|
|
|
QString p(path);
|
|
if (p.endsWith(constDirSep)) {
|
|
p=p.left(p.length()-1);
|
|
}
|
|
/* TODO: Display ~/Music or /home/user/Music / /Users/user/Music ???
|
|
p=homeToTilda(QDir::toNativeSeparators(p));
|
|
*/
|
|
return QDir::toNativeSeparators(isFolder && p.endsWith(constDirSep) ? p.left(p.length()-1) : p);
|
|
}
|
|
|
|
QString Utils::convertPathFromDisplay(const QString &path, bool isFolder)
|
|
{
|
|
QString p=path.trimmed();
|
|
if (p.isEmpty()) {
|
|
return p;
|
|
}
|
|
|
|
if (p.startsWith(constHttp)) {
|
|
return fixPath(p);
|
|
}
|
|
return tildaToHome(fixPath(QDir::fromNativeSeparators(p), isFolder));
|
|
}
|
|
|
|
#ifndef Q_OS_WIN
|
|
gid_t Utils::getGroupId(const char *groupName)
|
|
{
|
|
static bool init=false;
|
|
static gid_t gid=0;
|
|
|
|
if (init) {
|
|
return gid;
|
|
}
|
|
|
|
init=true;
|
|
|
|
// First of all see if current group is actually 'groupName'!!!
|
|
gid_t egid=getegid();
|
|
struct group *group=getgrgid(egid);
|
|
|
|
if (group && 0==strcmp(group->gr_name, groupName)) {
|
|
gid=egid;
|
|
return gid;
|
|
}
|
|
|
|
// Now see if user is a member of 'groupName'
|
|
struct passwd *pw=getpwuid(geteuid());
|
|
|
|
if (!pw) {
|
|
return gid;
|
|
}
|
|
|
|
group=getgrnam(groupName);
|
|
|
|
if (group) {
|
|
for (int i=0; group->gr_mem[i]; ++i) {
|
|
if (0==strcmp(group->gr_mem[i], pw->pw_name)) {
|
|
gid=group->gr_gid;
|
|
return gid;
|
|
}
|
|
}
|
|
}
|
|
return gid;
|
|
}
|
|
|
|
/*
|
|
* Set file permissions.
|
|
* If user is a memeber of "users" group, then set file as owned by and writeable by "users" group.
|
|
*/
|
|
void Utils::setFilePerms(const QString &file, const char *groupName)
|
|
{
|
|
//
|
|
// Clear any umask before setting file perms
|
|
mode_t oldMask(umask(0000));
|
|
gid_t gid=getGroupId(groupName);
|
|
QByteArray fn=QFile::encodeName(file);
|
|
::chmod(fn.constData(), 0==gid ? 0644 : 0664);
|
|
if (0!=gid) {
|
|
int rv=::chown(fn.constData(), geteuid(), gid);
|
|
Q_UNUSED(rv)
|
|
}
|
|
// Reset umask
|
|
::umask(oldMask);
|
|
}
|
|
#else
|
|
void Utils::setFilePerms(const QString &file, const char *groupName)
|
|
{
|
|
Q_UNUSED(file)
|
|
Q_UNUSED(groupName)
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Create directory, and set its permissions.
|
|
* If user is a memeber of "audio" group, then set dir as owned by and writeable by "audio" group.
|
|
*/
|
|
bool Utils::createWorldReadableDir(const QString &dir, const QString &base, const char *groupName)
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
Q_UNUSED(base)
|
|
Q_UNUSED(groupName)
|
|
return makeDir(dir, 0775);
|
|
#else
|
|
//
|
|
// Clear any umask before dir is created
|
|
mode_t oldMask(umask(0000));
|
|
gid_t gid=base.isEmpty() ? 0 : getGroupId(groupName);
|
|
bool status(makeDir(dir, 0==gid ? 0755 : 0775));
|
|
|
|
if (status && 0!=gid && dir.startsWith(base)) {
|
|
QStringList parts=dir.mid(base.length()).split(constDirSep);
|
|
QString d(base);
|
|
|
|
foreach (const QString &p, parts) {
|
|
d+=constDirSep+p;
|
|
int rv=::chown(QFile::encodeName(d).constData(), geteuid(), gid);
|
|
Q_UNUSED(rv)
|
|
}
|
|
}
|
|
// Reset umask
|
|
::umask(oldMask);
|
|
return status;
|
|
#endif
|
|
}
|
|
|
|
// Copied from KDE... START
|
|
#include <QLocale>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
|
|
// kde_file.h
|
|
#ifndef Q_OS_WIN
|
|
#if (defined _LFS64_LARGEFILE) && (defined _LARGEFILE64_SOURCE) && (!defined _GNU_SOURCE) && (!defined __sun)
|
|
#define KDE_stat ::stat64
|
|
#define KDE_lstat ::lstat64
|
|
#define KDE_struct_stat struct stat64
|
|
#define KDE_mkdir ::mkdir
|
|
#else
|
|
#define KDE_stat ::stat
|
|
#define KDE_lstat ::lstat
|
|
#define KDE_struct_stat struct stat
|
|
#define KDE_mkdir ::mkdir
|
|
#endif
|
|
#endif // Q_OS_WIN
|
|
|
|
// kstandarddirs.h
|
|
bool Utils::makeDir(const QString &dir, int mode)
|
|
{
|
|
// we want an absolute path
|
|
if (QDir::isRelativePath(dir)) {
|
|
return false;
|
|
}
|
|
|
|
#ifdef Q_OS_WIN
|
|
Q_UNUSED(mode)
|
|
return QDir().mkpath(dir);
|
|
#else
|
|
QString target = dir;
|
|
uint len = target.length();
|
|
|
|
// append trailing slash if missing
|
|
if (dir.at(len - 1) != QLatin1Char('/')) {
|
|
target += QLatin1Char('/');
|
|
}
|
|
|
|
QString base;
|
|
uint i = 1;
|
|
|
|
while ( i < len ) {
|
|
KDE_struct_stat st;
|
|
int pos = target.indexOf(QLatin1Char('/'), i);
|
|
base += target.mid(i - 1, pos - i + 1);
|
|
QByteArray baseEncoded = QFile::encodeName(base);
|
|
// bail out if we encountered a problem
|
|
if (KDE_stat(baseEncoded, &st) != 0) {
|
|
// Directory does not exist....
|
|
// Or maybe a dangling symlink ?
|
|
if (KDE_lstat(baseEncoded, &st) == 0)
|
|
(void)unlink(baseEncoded); // try removing
|
|
|
|
if (KDE_mkdir(baseEncoded, static_cast<mode_t>(mode)) != 0) {
|
|
baseEncoded.prepend( "trying to create local folder " );
|
|
perror(baseEncoded.constData());
|
|
return false; // Couldn't create it :-(
|
|
}
|
|
}
|
|
i = pos + 1;
|
|
}
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
QString Utils::formatByteSize(double size)
|
|
{
|
|
static bool useSiUnites=false;
|
|
static QLocale locale;
|
|
|
|
#ifndef Q_OS_WIN
|
|
static bool init=false;
|
|
if (!init) {
|
|
init=true;
|
|
const char *env=qgetenv("KDE_FULL_SESSION");
|
|
QString dm=env && 0==strcmp(env, "true") ? QLatin1String("KDE") : QString(qgetenv("XDG_CURRENT_DESKTOP"));
|
|
useSiUnites=!dm.isEmpty() && QLatin1String("KDE")!=dm;
|
|
}
|
|
#endif
|
|
int unit = 0;
|
|
double multiplier = useSiUnites ? 1000.0 : 1024.0;
|
|
|
|
while (qAbs(size) >= multiplier && unit < 3) {
|
|
size /= multiplier;
|
|
unit++;
|
|
}
|
|
|
|
if (useSiUnites) {
|
|
switch(unit) {
|
|
case 0: return QObject::tr("%1 B").arg(size);
|
|
case 1: return QObject::tr("%1 kB").arg(locale.toString(size, 'f', 1));
|
|
case 2: return QObject::tr("%1 MB").arg(locale.toString(size, 'f', 1));
|
|
default:
|
|
case 3: return QObject::tr("%1 GB").arg(locale.toString(size, 'f', 1));
|
|
}
|
|
} else {
|
|
switch(unit) {
|
|
case 0: return QObject::tr("%1 B").arg(size);
|
|
case 1: return QObject::tr("%1 KiB").arg(locale.toString(size, 'f', 1));
|
|
case 2: return QObject::tr("%1 MiB").arg(locale.toString(size, 'f', 1));
|
|
default:
|
|
case 3: return QObject::tr("%1 GiB").arg(locale.toString(size, 'f', 1));
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined Q_OS_WIN
|
|
#define KPATH_SEPARATOR ';'
|
|
// #define KDIR_SEPARATOR '\\' /* faster than QDir::separator() */
|
|
#else
|
|
#define KPATH_SEPARATOR ':'
|
|
// #define KDIR_SEPARATOR '/' /* faster than QDir::separator() */
|
|
#endif
|
|
|
|
static inline QString equalizePath(QString &str)
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
// filter pathes through QFileInfo to have always
|
|
// the same case for drive letters
|
|
QFileInfo f(str);
|
|
if (f.isAbsolute())
|
|
return f.absoluteFilePath();
|
|
else
|
|
#endif
|
|
return str;
|
|
}
|
|
|
|
static void tokenize(QStringList &tokens, const QString &str, const QString &delim)
|
|
{
|
|
const int len = str.length();
|
|
QString token;
|
|
|
|
for(int index = 0; index < len; index++) {
|
|
if (delim.contains(str[index])) {
|
|
tokens.append(equalizePath(token));
|
|
token.clear();
|
|
} else {
|
|
token += str[index];
|
|
}
|
|
}
|
|
if (!token.isEmpty()) {
|
|
tokens.append(equalizePath(token));
|
|
}
|
|
}
|
|
|
|
#ifdef Q_OS_WIN
|
|
static QStringList executableExtensions()
|
|
{
|
|
QStringList ret = QString::fromLocal8Bit(qgetenv("PATHEXT")).split(QLatin1Char(';'));
|
|
if (!ret.contains(QLatin1String(".exe"), Qt::CaseInsensitive)) {
|
|
// If %PATHEXT% does not contain .exe, it is either empty, malformed, or distorted in ways that we cannot support, anyway.
|
|
ret.clear();
|
|
ret << QLatin1String(".exe")
|
|
<< QLatin1String(".com")
|
|
<< QLatin1String(".bat")
|
|
<< QLatin1String(".cmd");
|
|
}
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static QStringList systemPaths(const QString &pstr)
|
|
{
|
|
QStringList tokens;
|
|
QString p = pstr;
|
|
|
|
if( p.isEmpty() ) {
|
|
p = QString::fromLocal8Bit( qgetenv( "PATH" ) );
|
|
}
|
|
|
|
QString delimiters(QLatin1Char(KPATH_SEPARATOR));
|
|
delimiters += QLatin1Char('\b');
|
|
tokenize( tokens, p, delimiters );
|
|
|
|
QStringList exePaths;
|
|
|
|
// split path using : or \b as delimiters
|
|
for( int i = 0; i < tokens.count(); i++ ) {
|
|
exePaths << /*KShell::tildeExpand(*/ tokens[ i ] /*)*/; // TODO
|
|
}
|
|
|
|
return exePaths;
|
|
}
|
|
|
|
#ifdef Q_OS_MAC
|
|
static QString getBundle(const QString &path)
|
|
{
|
|
//kDebug(180) << "getBundle(" << path << ", " << ignore << ") called";
|
|
QFileInfo info;
|
|
QString bundle = path;
|
|
bundle += QLatin1String(".app/Contents/MacOS/") + bundle.section(QLatin1Char('/'), -1);
|
|
info.setFile( bundle );
|
|
FILE *file;
|
|
if ((file = fopen(info.absoluteFilePath().toUtf8().constData(), "r"))) {
|
|
fclose(file);
|
|
struct stat _stat;
|
|
if ((stat(info.absoluteFilePath().toUtf8().constData(), &_stat)) < 0) {
|
|
return QString();
|
|
}
|
|
if ( _stat.st_mode & S_IXUSR ) {
|
|
if ( ((_stat.st_mode & S_IFMT) == S_IFREG) || ((_stat.st_mode & S_IFMT) == S_IFLNK) ) {
|
|
//kDebug(180) << "getBundle(): returning " << bundle;
|
|
return bundle;
|
|
}
|
|
}
|
|
}
|
|
return QString();
|
|
}
|
|
#endif
|
|
|
|
static QString checkExecutable( const QString& path )
|
|
{
|
|
#ifdef Q_OS_MAC
|
|
QString bundle = getBundle( path );
|
|
if ( !bundle.isEmpty() ) {
|
|
//kDebug(180) << "findExe(): returning " << bundle;
|
|
return bundle;
|
|
}
|
|
#endif
|
|
QFileInfo info( path );
|
|
QFileInfo orig = info;
|
|
#if defined(Q_OS_DARWIN) || defined(Q_OS_MAC)
|
|
FILE *file;
|
|
if ((file = fopen(orig.absoluteFilePath().toUtf8().constData(), "r"))) {
|
|
fclose(file);
|
|
struct stat _stat;
|
|
if ((stat(orig.absoluteFilePath().toUtf8().constData(), &_stat)) < 0) {
|
|
return QString();
|
|
}
|
|
if ( _stat.st_mode & S_IXUSR ) {
|
|
if ( ((_stat.st_mode & S_IFMT) == S_IFREG) || ((_stat.st_mode & S_IFMT) == S_IFLNK) ) {
|
|
orig.makeAbsolute();
|
|
return orig.filePath();
|
|
}
|
|
}
|
|
}
|
|
return QString();
|
|
#else
|
|
if( info.exists() && info.isSymLink() )
|
|
info = QFileInfo( info.canonicalFilePath() );
|
|
if( info.exists() && info.isExecutable() && info.isFile() ) {
|
|
// return absolute path, but without symlinks resolved in order to prevent
|
|
// problems with executables that work differently depending on name they are
|
|
// run as (for example gunzip)
|
|
orig.makeAbsolute();
|
|
return orig.filePath();
|
|
}
|
|
//kDebug(180) << "checkExecutable(): failed, returning empty string";
|
|
return QString();
|
|
#endif
|
|
}
|
|
|
|
QString Utils::findExe(const QString &appname, const QString &pstr)
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
QStringList executable_extensions = executableExtensions();
|
|
if (!executable_extensions.contains(appname.section(QLatin1Char('.'), -1, -1, QString::SectionIncludeLeadingSep), Qt::CaseInsensitive)) {
|
|
QString found_exe;
|
|
foreach (const QString& extension, executable_extensions) {
|
|
found_exe = findExe(appname + extension, pstr);
|
|
if (!found_exe.isEmpty()) {
|
|
return found_exe;
|
|
}
|
|
}
|
|
return QString();
|
|
}
|
|
#endif
|
|
|
|
const QStringList exePaths = systemPaths( pstr );
|
|
for (QStringList::ConstIterator it = exePaths.begin(); it != exePaths.end(); ++it) {
|
|
QString p = (*it) + QLatin1Char('/');
|
|
p += appname;
|
|
|
|
QString result = checkExecutable(p);
|
|
if (!result.isEmpty()) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return QString();
|
|
}
|
|
// Copied from KDE... END
|
|
|
|
QString Utils::formatDuration(const quint32 totalseconds)
|
|
{
|
|
//Get the days,hours,minutes and seconds out of the total seconds
|
|
quint32 days = totalseconds / 86400;
|
|
quint32 rest = totalseconds - (days * 86400);
|
|
quint32 hours = rest / 3600;
|
|
rest = rest - (hours * 3600);
|
|
quint32 minutes = rest / 60;
|
|
quint32 seconds = rest - (minutes * 60);
|
|
|
|
//Convert hour,minutes and seconds to a QTime for easier parsing
|
|
QTime time(hours, minutes, seconds);
|
|
|
|
return 0==days
|
|
? time.toString("h:mm:ss")
|
|
: QString("%1:%2").arg(days).arg(time.toString("hh:mm:ss"));
|
|
}
|
|
|
|
QString Utils::formatTime(const quint32 seconds, bool zeroIsUnknown)
|
|
{
|
|
if (0==seconds && zeroIsUnknown) {
|
|
return QObject::tr("Unknown");
|
|
}
|
|
|
|
static const quint32 constHour=60*60;
|
|
if (seconds>constHour) {
|
|
return Utils::formatDuration(seconds);
|
|
}
|
|
|
|
QString result(QString::number(floor(seconds / 60.0))+QChar(':'));
|
|
if (seconds % 60 < 10) {
|
|
result += "0";
|
|
}
|
|
return result+QString::number(seconds % 60);
|
|
}
|
|
|
|
QString Utils::cleanPath(const QString &p)
|
|
{
|
|
QString path(p);
|
|
while (path.contains("//")) {
|
|
path.replace("//", constDirSepStr);
|
|
}
|
|
return fixPath(path);
|
|
}
|
|
|
|
static QString userDir(const QString &mainDir, const QString &sub, bool create)
|
|
{
|
|
QString dir=mainDir;
|
|
if (!sub.isEmpty()) {
|
|
dir+=sub;
|
|
}
|
|
dir=Utils::cleanPath(dir);
|
|
QDir d(dir);
|
|
return d.exists() || (create && d.mkpath(dir)) ? dir : QString();
|
|
}
|
|
|
|
QString Utils::dataDir(const QString &sub, bool create)
|
|
{
|
|
#if defined Q_OS_WIN || defined Q_OS_MAC
|
|
|
|
return userDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation)+constDirSep, sub, create);
|
|
|
|
#else
|
|
|
|
static QString location;
|
|
if (location.isEmpty()) {
|
|
location=QStandardPaths::writableLocation(QStandardPaths::DataLocation);
|
|
if (QCoreApplication::organizationName()==QCoreApplication::applicationName()) {
|
|
location=location.replace(QCoreApplication::organizationName()+Utils::constDirSep+QCoreApplication::applicationName(),
|
|
QCoreApplication::applicationName());
|
|
}
|
|
}
|
|
return userDir(location+constDirSep, sub, create);
|
|
|
|
#endif
|
|
}
|
|
|
|
QString Utils::cacheDir(const QString &sub, bool create)
|
|
{
|
|
#if defined Q_OS_WIN || defined Q_OS_MAC
|
|
|
|
return userDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)+constDirSep, sub, create);
|
|
|
|
#else
|
|
|
|
static QString location;
|
|
if (location.isEmpty()) {
|
|
location=QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
|
|
if (QCoreApplication::organizationName()==QCoreApplication::applicationName()) {
|
|
location=location.replace(QCoreApplication::organizationName()+Utils::constDirSep+QCoreApplication::applicationName(),
|
|
QCoreApplication::applicationName());
|
|
}
|
|
}
|
|
return userDir(location+constDirSep, sub, create);
|
|
|
|
#endif
|
|
}
|
|
|
|
QString Utils::systemDir(const QString &sub)
|
|
{
|
|
#if defined Q_OS_WIN
|
|
return fixPath(QCoreApplication::applicationDirPath())+(sub.isEmpty() ? QString() : (sub+constDirSep));
|
|
#elif defined Q_OS_MAC
|
|
return fixPath(QCoreApplication::applicationDirPath())+QLatin1String("../Resources/")+(sub.isEmpty() ? QString() : (sub+constDirSep));
|
|
#else
|
|
return fixPath(QString(SHARE_INSTALL_PREFIX"/")+QCoreApplication::applicationName()+constDirSep+(sub.isEmpty() ? QString() : sub));
|
|
#endif
|
|
}
|
|
|
|
QString Utils::helper(const QString &app)
|
|
{
|
|
#if defined Q_OS_WIN
|
|
return fixPath(QCoreApplication::applicationDirPath())+app+QLatin1String(".exe");
|
|
#elif defined Q_OS_MAC
|
|
return fixPath(QCoreApplication::applicationDirPath())+app;
|
|
#else
|
|
return QString(INSTALL_PREFIX "/" LINUX_LIB_DIR "/")+QCoreApplication::applicationName()+constDirSep+app;
|
|
#endif
|
|
}
|
|
|
|
bool Utils::moveFile(const QString &from, const QString &to)
|
|
{
|
|
return !from.isEmpty() && !to.isEmpty() && from!=to && QFile::exists(from) && !QFile::exists(to) && QFile::rename(from, to);
|
|
}
|
|
|
|
void Utils::moveDir(const QString &from, const QString &to)
|
|
{
|
|
if (from.isEmpty() || to.isEmpty() || from==to) {
|
|
return;
|
|
}
|
|
|
|
QDir f(from);
|
|
if (!f.exists()) {
|
|
return;
|
|
}
|
|
|
|
QDir t(to);
|
|
if (!t.exists()) {
|
|
return;
|
|
}
|
|
|
|
QFileInfoList files=f.entryInfoList(QStringList() << "*", QDir::Files|QDir::Dirs|QDir::NoDotAndDotDot);
|
|
foreach (const QFileInfo &file, files) {
|
|
if (file.isDir()) {
|
|
QString dest=to+file.fileName()+constDirSep;
|
|
if (!QDir(dest).exists()) {
|
|
t.mkdir(file.fileName());
|
|
}
|
|
moveDir(from+file.fileName()+constDirSep, dest);
|
|
} else {
|
|
QFile::rename(from+file.fileName(), to+file.fileName());
|
|
}
|
|
}
|
|
|
|
f.cdUp();
|
|
f.rmdir(from);
|
|
}
|
|
|
|
void Utils::clearOldCache(const QString &sub, int maxAge)
|
|
{
|
|
if (sub.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QString d=cacheDir(sub, false);
|
|
if (d.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QDir dir(d);
|
|
if (dir.exists()) {
|
|
QFileInfoList files=dir.entryInfoList(QDir::Files|QDir::NoDotAndDotDot);
|
|
if (files.count()) {
|
|
QDateTime now=QDateTime::currentDateTime();
|
|
foreach (const QFileInfo &f, files) {
|
|
if (f.lastModified().daysTo(now)>maxAge) {
|
|
QFile::remove(f.absoluteFilePath());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Utils::touchFile(const QString &fileName)
|
|
{
|
|
::utime(QFile::encodeName(fileName).constData(), 0);
|
|
}
|
|
|
|
double Utils::smallFontFactor(const QFont &f)
|
|
{
|
|
double sz=f.pointSizeF();
|
|
if (sz<=8.5) {
|
|
return 1.0;
|
|
}
|
|
if (sz<=9.0) {
|
|
return 0.9;
|
|
}
|
|
return 0.85;
|
|
}
|
|
|
|
QFont Utils::smallFont(QFont f)
|
|
{
|
|
f.setPointSizeF(f.pointSizeF()*smallFontFactor(f));
|
|
return f;
|
|
}
|
|
|
|
int Utils::layoutSpacing(QWidget *w)
|
|
{
|
|
int spacing=(w ? w->style() : qApp->style())->layoutSpacing(QSizePolicy::DefaultType, QSizePolicy::DefaultType, Qt::Vertical);
|
|
if (spacing<0) {
|
|
spacing=scaleForDpi(4);
|
|
}
|
|
return spacing;
|
|
}
|
|
|
|
double Utils::screenDpiScale()
|
|
{
|
|
static double scaleFactor=-1.0;
|
|
if (scaleFactor<0) {
|
|
QWidget *dw=QApplication::desktop();
|
|
if (!dw) {
|
|
return 1.0;
|
|
}
|
|
scaleFactor=dw->logicalDpiX()>120 ? qMin(qMax(dw->logicalDpiX()/96.0, 1.0), 4.0) : 1.0;
|
|
}
|
|
return scaleFactor;
|
|
}
|
|
|
|
bool Utils::limitedHeight(QWidget *w)
|
|
{
|
|
static bool init=false;
|
|
static bool limited=false;
|
|
if (!init) {
|
|
limited=!qgetenv("CANTATA_NETBOOK").isEmpty();
|
|
if (!limited) {
|
|
QDesktopWidget *dw=QApplication::desktop();
|
|
if (dw) {
|
|
limited=dw->availableGeometry(w).size().height()<=800;
|
|
}
|
|
}
|
|
}
|
|
return limited;
|
|
}
|
|
|
|
void Utils::resizeWindow(QWidget *w, bool preserveWidth, bool preserveHeight)
|
|
{
|
|
QWidget *window=w ? w->window() : 0;
|
|
if (window) {
|
|
QSize was=window->size();
|
|
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
|
window->setMinimumSize(QSize(0, 0));
|
|
window->adjustSize();
|
|
QSize now=window->size();
|
|
window->setMinimumSize(now);
|
|
if (preserveWidth && preserveHeight) {
|
|
window->resize(qMax(was.width(), now.width()), qMax(was.height(), now.height()));
|
|
} else if (preserveWidth) {
|
|
window->resize(qMax(was.width(), now.width()), now.height());
|
|
} else if (preserveHeight) {
|
|
window->resize(now.width(), qMax(was.height(), now.height()));
|
|
}
|
|
}
|
|
}
|
|
|
|
Utils::Desktop Utils::currentDe()
|
|
{
|
|
#if !defined Q_OS_WIN && !defined Q_OS_MAC
|
|
static int de=-1;
|
|
if (-1==de) {
|
|
de=Other;
|
|
QByteArray desktop=qgetenv("XDG_CURRENT_DESKTOP").toLower();
|
|
if ("unity"==desktop) {
|
|
de=Unity;
|
|
} else if ("kde"==desktop) {
|
|
de=KDE;
|
|
} else if ("gnome"==desktop || "pantheon"==desktop) {
|
|
de=Gnome;
|
|
} else {
|
|
QByteArray kde=qgetenv("KDE_FULL_SESSION");
|
|
if ("true"==kde) {
|
|
de=KDE;
|
|
}
|
|
}
|
|
}
|
|
return (Utils::Desktop)de;
|
|
#endif
|
|
return Other;
|
|
}
|
|
|
|
QPainterPath Utils::buildPath(const QRectF &r, double radius)
|
|
{
|
|
QPainterPath path;
|
|
double diameter(radius*2);
|
|
|
|
path.moveTo(r.x()+r.width(), r.y()+r.height()-radius);
|
|
path.arcTo(r.x()+r.width()-diameter, r.y(), diameter, diameter, 0, 90);
|
|
path.arcTo(r.x(), r.y(), diameter, diameter, 90, 90);
|
|
path.arcTo(r.x(), r.y()+r.height()-diameter, diameter, diameter, 180, 90);
|
|
path.arcTo(r.x()+r.width()-diameter, r.y()+r.height()-diameter, diameter, diameter, 270, 90);
|
|
return path;
|
|
}
|
|
|
|
QColor Utils::clampColor(const QColor &col)
|
|
{
|
|
static const int constMin=64;
|
|
static const int constMax=240;
|
|
|
|
if (col.value()<constMin) {
|
|
return QColor(constMin, constMin, constMin);
|
|
} else if (col.value()>constMax) {
|
|
return QColor(constMax, constMax, constMax);
|
|
}
|
|
return col;
|
|
}
|
|
|
|
QColor Utils::monoIconColor()
|
|
{
|
|
return clampColor(QApplication::palette().color(QPalette::Active, QPalette::WindowText));
|
|
}
|
|
|
|
#ifdef Q_OS_WIN
|
|
// This is down here, because windows.h includes ALL windows stuff - and we get conflicts with MessageBox :-(
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
void Utils::raiseWindow(QWidget *w)
|
|
{
|
|
if (!w) {
|
|
return;
|
|
}
|
|
|
|
#ifdef Q_OS_WIN
|
|
::SetWindowPos(reinterpret_cast<HWND>(w->effectiveWinId()), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
|
|
::SetWindowPos(reinterpret_cast<HWND>(w->effectiveWinId()), HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
|
|
#elif !defined Q_OS_WIN
|
|
bool wasHidden=w->isHidden();
|
|
#endif
|
|
|
|
w->raise();
|
|
w->showNormal();
|
|
w->activateWindow();
|
|
#ifdef Q_OS_MAC
|
|
w->raise();
|
|
#endif
|
|
#if !defined Q_OS_WIN && !defined Q_OS_MAC
|
|
// This section seems to be required for compiz, so that MPRIS.Raise actually shows the window, and not just highlight launcher.
|
|
QString wmctrl=Utils::findExe(QLatin1String("wmctrl"));
|
|
if (!wmctrl.isEmpty()) {
|
|
if (wasHidden) {
|
|
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
|
}
|
|
QProcess::execute(wmctrl, QStringList() << QLatin1String("-i") << QLatin1String("-a") << QString::number(w->effectiveWinId()));
|
|
}
|
|
#endif
|
|
}
|
|
|