Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Qt/Necessitas - reasonable QFileDialog replacement/skin?

I'm looking for a nice way to address porting Qt applications to Qt/Necessitas (Android).

Some of the QtGUI widgets are absolutely atrocious - unfortunately, including QFileDialog.

Do you know of any replacements with a proper look and feel? Is making QFileDialog usable anywhere near high priority for Necessitas developers?

#include <QApplication>

#include <QFileDialog>

int main(int argc, char* argv[]) {
    QApplication a(argc, argv);

    QString fileName = QFileDialog::getOpenFileName(NULL,
      QObject::tr("Open Image"), "/home/jana", QObject::tr("Image Files (*.png *.jpg *.bmp)"));

    a.exec();
}

QFileDialog

like image 303
qdot Avatar asked Feb 26 '13 00:02

qdot


2 Answers

Android does not have own, native file dialog. We can use QtAndroidExtras to invoke external application which is able to pick a file:

chosing external application

I wrote wrapper, that could be used for that. Here's full code:

androidfiledialog.h

#ifndef ANDROIDFILEDIALOG_H
#define ANDROIDFILEDIALOG_H

#include <QObject>
#include <QAndroidJniObject>
#include <QtAndroid>
#include <QAndroidActivityResultReceiver>

class AndroidFileDialog : public QObject
{
    Q_OBJECT

public:
    explicit AndroidFileDialog(QObject *parent = 0);
    virtual ~AndroidFileDialog();
    bool provideExistingFileName();

private:
    class ResultReceiver : public QAndroidActivityResultReceiver {
        AndroidFileDialog *_dialog;
    public:
        ResultReceiver(AndroidFileDialog *dialog);
        virtual ~ResultReceiver();
        void handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data);
        QString uriToPath(QAndroidJniObject uri);
    };

    static const int EXISTING_FILE_NAME_REQUEST = 1;
    ResultReceiver *receiver;
    void emitExistingFileNameReady(QString result);

signals:
    void existingFileNameReady(QString result);
};

#endif // ANDROIDFILEDIALOG_H

androidfiledialog.cpp

#include "androidfiledialog.h"

AndroidFileDialog::ResultReceiver::ResultReceiver(AndroidFileDialog *dialog) : _dialog(dialog) {}
AndroidFileDialog::ResultReceiver::~ResultReceiver() {}

void AndroidFileDialog::ResultReceiver::handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data)
{
    jint RESULT_OK = QAndroidJniObject::getStaticField<jint>("android/app/Activity", "RESULT_OK");
    if (receiverRequestCode == EXISTING_FILE_NAME_REQUEST && resultCode == RESULT_OK) {
        QAndroidJniObject uri = data.callObjectMethod("getData", "()Landroid/net/Uri;");
        QString path = uriToPath(uri);
        _dialog->emitExistingFileNameReady(path);
    } else {
        _dialog->emitExistingFileNameReady(QString());
    }
}

QString AndroidFileDialog::ResultReceiver::uriToPath(QAndroidJniObject uri)
{
    if (uri.toString().startsWith("file:", Qt::CaseInsensitive)) {
        return uri.callObjectMethod("getPath", "()Ljava/lang/String;").toString();
    } else {
        QAndroidJniObject contentResolver = QtAndroid::androidActivity().callObjectMethod("getContentResolver", "()Landroid/content/ContentResolver;");
        QAndroidJniObject cursor = contentResolver.callObjectMethod("query", "(Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;", uri.object<jobject>(), 0, 0, 0, 0);
        QAndroidJniObject DATA = QAndroidJniObject::fromString("_data");
        jint columnIndex = cursor.callMethod<jint>("getColumnIndexOrThrow", "(Ljava/lang/String;)I", DATA.object<jstring>());
        cursor.callMethod<jboolean>("moveToFirst", "()Z");
        QAndroidJniObject result = cursor.callObjectMethod("getString", "(I)Ljava/lang/String;", columnIndex);
        return result.isValid() ? result.toString() : QString();
    }
}

AndroidFileDialog::AndroidFileDialog(QObject *parent) : QObject(parent)
{
    receiver = new ResultReceiver(this);
}

AndroidFileDialog::~AndroidFileDialog()
{
    delete receiver;
}

bool AndroidFileDialog::provideExistingFileName()
{
    QAndroidJniObject ACTION_GET_CONTENT = QAndroidJniObject::fromString("android.intent.action.GET_CONTENT");
    QAndroidJniObject intent("android/content/Intent");
    if (ACTION_GET_CONTENT.isValid() && intent.isValid()) {
        intent.callObjectMethod("setAction", "(Ljava/lang/String;)Landroid/content/Intent;", ACTION_GET_CONTENT.object<jstring>());
        intent.callObjectMethod("setType", "(Ljava/lang/String;)Landroid/content/Intent;", QAndroidJniObject::fromString("file/*").object<jstring>());
        QtAndroid::startActivity(intent.object<jobject>(), EXISTING_FILE_NAME_REQUEST, receiver);
        return true;
    } else {
        return false;
    }
}

void AndroidFileDialog::emitExistingFileNameReady(QString result)
{
    emit existingFileNameReady(result);
}

You have to add to your *.pro file:

QT += androidextras

using example:

AndroidFileDialog *fileDialog = new AndroidFileDialog();
connect(fileDialog, SIGNAL(existingFileNameReady(QString)), this, SLOT(openFileNameReady(QString)));
bool success = fileDialog->provideExistingFileName();
if (!success) {
    qDebug() << "Problem with JNI or sth like that...";
    disconnect(fileDialog, SIGNAL(existingFileNameReady(QString)), this, SLOT(openFileNameReady(QString)));
    //or just delete fileDialog instead of disconnect
}

slot implementation:

void MyClass::openFileNameReady(QString fileName)
{
    if (!fileName.isNull()) {
        qDebug() << "FileName: " << fileName;
    } else {
        qDebug() << "User did not choose file";
    }
}

Please confirm this solution works properly on your device.

like image 93
Mariusz Jaskółka Avatar answered Oct 03 '22 07:10

Mariusz Jaskółka


You could easily build your own file dialog either with QtWidgets or QML, by using the out-of-the-box QFileSystemModel class or the FolderListModel element.

As for whether it is priority or not, at this point it seems that Necessitas will be absorbed by Digia's efforts to support Android. I doubt there will be significant efforts to style QtWidgets appropriately, since the module is marked as DONE and all the focus for UI is on QML. So, I wouldn't hold by breath if I were you. Plus the stock Qt widgets look completely ugly on non-desktop platforms.

like image 26
dtech Avatar answered Oct 03 '22 08:10

dtech