Add tilde (both ~ and ~<user>) expansion to QFileDialog on UNIX.
authorPierre Rossi <pierre.rossi@nokia.com>
Fri, 20 May 2011 12:16:33 +0000 (14:16 +0200)
committerQt Continuous Integration System <qt-info@nokia.com>
Wed, 8 Jun 2011 20:37:52 +0000 (22:37 +0200)
Task-number: QTBUG-3265

Change-Id: Id8062afe69e798e1f9cf3f4e967ae0d30c362b72
Reviewed-on: http://codereview.qt.nokia.com/411
Reviewed-by: Pierre Rossi <pierre.rossi@gmail.com>
src/gui/dialogs/qfiledialog.cpp
tests/auto/qfiledialog/tst_qfiledialog.cpp

index c22af65..2e57a84 100644 (file)
@@ -66,6 +66,9 @@
 #if defined(Q_OS_WINCE)
 extern bool qt_priv_ptr_valid;
 #endif
+#if defined(Q_OS_UNIX)
+#include <pwd.h>
+#endif
 #endif
 
 QT_BEGIN_NAMESPACE
@@ -858,23 +861,78 @@ void QFileDialog::selectFile(const QString &filename)
         d->lineEdit()->setText(file);
 }
 
+#ifdef Q_OS_UNIX
+Q_AUTOTEST_EXPORT QString qt_tildeExpansion(const QString &path, bool *expanded = 0)
+{
+    if (expanded != 0)
+        *expanded = false;
+    if (!path.startsWith(QLatin1Char('~')))
+        return path;
+    QString ret = path;
+    QStringList tokens = ret.split(QDir::separator());
+    if (tokens.first() == QLatin1String("~")) {
+        ret.replace(0, 1, QDir::homeDirPath());
+    } else {
+        QString userName = tokens.first();
+        userName.remove(0, 1);
+#if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD)
+        passwd pw;
+        passwd *tmpPw;
+        char buf[200];
+        const int bufSize = sizeof(buf);
+        int err = getpwnam_r(userName.toLocal8Bit().constData(), &pw, buf, bufSize, &tmpPw);
+        if (err || !tmpPw)
+            return ret;
+        const QString homePath = QString::fromLocal8Bit(pw.pw_dir);
+#else
+        passwd *pw = getpwnam(userName.toLocal8Bit().constData());
+        if (!pw)
+            return ret;
+        const QString homePath = QString::fromLocal8Bit(pw->pw_dir);
+#endif
+        ret.replace(0, tokens.first().length(), homePath);
+    }
+    if (expanded != 0)
+        *expanded = true;
+    return ret;
+}
+#endif
+
 /**
     Returns the text in the line edit which can be one or more file names
   */
 QStringList QFileDialogPrivate::typedFiles() const
 {
+    Q_Q(const QFileDialog);
     QStringList files;
     QString editText = lineEdit()->text();
-    if (!editText.contains(QLatin1Char('"')))
+    if (!editText.contains(QLatin1Char('"'))) {
+#ifdef Q_OS_UNIX
+        const QString prefix = q->directory().absolutePath() + QDir::separator();
+        if (QFile::exists(prefix + editText))
+            files << editText;
+        else
+            files << qt_tildeExpansion(editText);
+#else
         files << editText;
-    else {
+#endif
+    } else {
         // " is used to separate files like so: "file1" "file2" "file3" ...
         // ### need escape character for filenames with quotes (")
         QStringList tokens = editText.split(QLatin1Char('\"'));
         for (int i=0; i<tokens.size(); ++i) {
             if ((i % 2) == 0)
                 continue; // Every even token is a separator
+#ifdef Q_OS_UNIX
+            const QString token = tokens.at(i);
+            const QString prefix = q->directory().absolutePath() + QDir::separator();
+            if (QFile::exists(prefix + token))
+                files << token;
+            else
+                files << qt_tildeExpansion(token);
+#else
             files << toInternal(tokens.at(i));
+#endif
         }
     }
     return addDefaultSuffixToFiles(files);
@@ -3338,6 +3396,17 @@ QStringList QFSCompleter::splitPath(const QString &path) const
         pathCopy = pathCopy.mid(2);
     else
         doubleSlash.clear();
+#elif defined(Q_OS_UNIX)
+    bool expanded;
+    pathCopy = qt_tildeExpansion(pathCopy, &expanded);
+    if (expanded) {
+        QFileSystemModel *dirModel;
+        if (proxyModel)
+            dirModel = qobject_cast<QFileSystemModel *>(proxyModel->sourceModel());
+        else
+            dirModel = sourceModel;
+        dirModel->fetchMore(dirModel->index(pathCopy));
+    }
 #endif
 
     QRegExp re(QLatin1Char('[') + QRegExp::escape(sep) + QLatin1Char(']'));
@@ -3354,14 +3423,14 @@ QStringList QFSCompleter::splitPath(const QString &path) const
         parts.append(QString());
 #else
     QStringList parts = pathCopy.split(re);
-    if (path[0] == sep[0]) // read the "/" at the beginning as the split removed it
+    if (pathCopy[0] == sep[0]) // read the "/" at the beginning as the split removed it
         parts[0] = sep[0];
 #endif
 
 #if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN)
     bool startsFromRoot = !parts.isEmpty() && parts[0].endsWith(QLatin1Char(':'));
 #else
-    bool startsFromRoot = path[0] == sep[0];
+    bool startsFromRoot = pathCopy[0] == sep[0];
 #endif
     if (parts.count() == 1 || (parts.count() > 1 && !startsFromRoot)) {
         const QFileSystemModel *dirModel;
index bc076c5..08d2e88 100644 (file)
 # define STRINGIFY(x) #x
 # define TOSTRING(x) STRINGIFY(x)
 # define SRCDIR "C:/Private/" TOSTRING(SYMBIAN_SRCDIR_UID) "/"
+#elif defined(Q_OS_UNIX)
+#ifdef QT_BUILD_INTERNAL
+extern Q_GUI_EXPORT QString qt_tildeExpansion(const QString &path, bool *expanded = 0);
+#endif
 #endif
 
 class QNonNativeFileDialog : public QFileDialog
@@ -144,6 +148,10 @@ private slots:
     void clearLineEdit();
     void enableChooseButton();
     void hooks();
+#ifdef Q_OS_UNIX
+    void tildeExpansion_data();
+    void tildeExpansion();
+#endif
 
 private:
     QByteArray userSettings;
@@ -1323,5 +1331,36 @@ void tst_QFiledialog::hooks()
     QCOMPARE(QFileDialog::getSaveFileName(), QString("saveName"));
 }
 
+#ifdef Q_OS_UNIX
+void tst_QFiledialog::tildeExpansion_data()
+{
+    QTest::addColumn<QString>("tildePath");
+    QTest::addColumn<QString>("expandedPath");
+
+    QTest::newRow("empty path") << QString() << QString();
+    QTest::newRow("~") << QString::fromLatin1("~") << QDir::homePath();
+    QTest::newRow("~/some/sub/dir/") << QString::fromLatin1("~/some/sub/dir") << QDir::homePath()
+                                        + QString::fromLatin1("/some/sub/dir");
+    QString userHome = QString(qgetenv("USER"));
+    userHome.prepend('~');
+    QTest::newRow("current user (~<user> syntax)") << userHome << QDir::homePath();
+    QString invalid = QString::fromLatin1("~thisIsNotAValidUserName");
+    QTest::newRow("invalid user name") << invalid << invalid;
+}
+
+
+void tst_QFiledialog::tildeExpansion()
+{
+#ifndef QT_BUILD_INTERNAL
+    QSKIP("Test case relies on developer build (AUTOTEST_EXPORT)", SkipAll);
+#else
+    QFETCH(QString, tildePath);
+    QFETCH(QString, expandedPath);
+
+    QCOMPARE(qt_tildeExpansion(tildePath), expandedPath);
+#endif
+}
+#endif
+
 QTEST_MAIN(tst_QFiledialog)
 #include "tst_qfiledialog.moc"