Skycoder42
7 years ago
12 changed files with 514 additions and 21 deletions
@ -1,5 +1,44 @@ |
|||
import QtQuick 2.0 |
|||
import QtQuick 2.10 |
|||
import de.skycoder42.QtMvvm.Core 1.0 |
|||
import de.skycoder42.QtMvvm.Quick 1.0 |
|||
|
|||
Item { |
|||
FileChooser { |
|||
id: _fileChooser |
|||
|
|||
property var msgConfig |
|||
property MessageResult msgResult |
|||
|
|||
signal closed() |
|||
|
|||
title: msgConfig.title |
|||
folderUrl: msgConfig.defaultValue |
|||
type: { |
|||
if(msgConfig.subType == "open") |
|||
return FileChooser.OpenDocument; |
|||
else if(msgConfig.subType == "files") |
|||
return FileChooser.OpenMultipleDocuments; |
|||
else if(msgConfig.subType == "save") |
|||
return FileChooser.CreateDocument; |
|||
else if(msgConfig.subType == "get") //special value for android only |
|||
return FileChooser.GetContent; |
|||
else { |
|||
return FileChooser.OpenDocument; |
|||
} |
|||
} |
|||
|
|||
onAccepted: { |
|||
if(msgResult) { |
|||
msgResult.complete(MessageConfig.Ok, result); |
|||
msgResult = null; |
|||
} |
|||
closed(); |
|||
} |
|||
|
|||
onRejected: { |
|||
if(msgResult) { |
|||
msgResult.complete(MessageConfig.Cancel); |
|||
msgResult = null; |
|||
} |
|||
closed(); |
|||
} |
|||
} |
|||
|
@ -1,5 +1,32 @@ |
|||
import QtQuick 2.0 |
|||
import QtQuick 2.10 |
|||
import de.skycoder42.QtMvvm.Core 1.0 |
|||
import de.skycoder42.QtMvvm.Quick 1.0 |
|||
|
|||
Item { |
|||
FileChooser { |
|||
id: _folderChooser |
|||
|
|||
property var msgConfig |
|||
property MessageResult msgResult |
|||
|
|||
signal closed() |
|||
|
|||
title: msgConfig.title |
|||
folderUrl: msgConfig.defaultValue |
|||
type: FileChooser.OpenDocumentTree |
|||
|
|||
onAccepted: { |
|||
if(msgResult) { |
|||
msgResult.complete(MessageConfig.Ok, result); |
|||
msgResult = null; |
|||
} |
|||
closed(); |
|||
} |
|||
|
|||
onRejected: { |
|||
if(msgResult) { |
|||
msgResult.complete(MessageConfig.Cancel); |
|||
msgResult = null; |
|||
} |
|||
closed(); |
|||
} |
|||
} |
|||
|
@ -0,0 +1,304 @@ |
|||
#include "androidfilechooser.h" |
|||
#include <QtAndroidExtras/QtAndroid> |
|||
#include <QtAndroidExtras/QAndroidJniEnvironment> |
|||
using namespace QtMvvm; |
|||
|
|||
AndroidFileChooser::AndroidFileChooser(QObject *parent) : |
|||
QObject(parent), |
|||
_title(), |
|||
_folderUrl(), |
|||
_type(OpenDocument), |
|||
_mimeTypes(QStringLiteral("*/*")), |
|||
_flags(OpenableFlag | AlwaysGrantWriteFlag), |
|||
_active(false), |
|||
_result() |
|||
{} |
|||
|
|||
AndroidFileChooser::~AndroidFileChooser() |
|||
{ |
|||
qt_noop(); |
|||
} |
|||
|
|||
QString AndroidFileChooser::title() const |
|||
{ |
|||
return _title; |
|||
} |
|||
|
|||
QUrl AndroidFileChooser::folderUrl() const |
|||
{ |
|||
return _folderUrl; |
|||
} |
|||
|
|||
AndroidFileChooser::ChooserType AndroidFileChooser::type() const |
|||
{ |
|||
return _type; |
|||
} |
|||
|
|||
QStringList AndroidFileChooser::mimeTypes() const |
|||
{ |
|||
return _mimeTypes; |
|||
} |
|||
|
|||
AndroidFileChooser::ChooserFlags AndroidFileChooser::chooserFlags() const |
|||
{ |
|||
return _flags; |
|||
} |
|||
|
|||
QVariant AndroidFileChooser::result() const |
|||
{ |
|||
return _result; |
|||
} |
|||
|
|||
void AndroidFileChooser::open() |
|||
{ |
|||
if(_active) |
|||
return; |
|||
|
|||
QAndroidJniObject intent; |
|||
switch (_type) { |
|||
case GetContent: |
|||
intent = createGetIntent(); |
|||
break; |
|||
case OpenDocument: |
|||
intent = createOpenIntent(); |
|||
break; |
|||
case OpenMultipleDocuments: |
|||
intent = createOpenMultipleIntent(); |
|||
break; |
|||
case CreateDocument: |
|||
intent = createSaveIntent(); |
|||
break; |
|||
case OpenDocumentTree: |
|||
intent = createOpenTreeIntent(); |
|||
break; |
|||
default: |
|||
Q_UNREACHABLE(); |
|||
break; |
|||
} |
|||
|
|||
auto chooserIntent = QAndroidJniObject::callStaticObjectMethod("android/content/Intent", "createChooser", |
|||
"(Landroid/content/Intent;Ljava/lang/CharSequence;)Landroid/content/Intent;", |
|||
intent.object(), |
|||
QAndroidJniObject::fromString(_title).object()); |
|||
_active = true; |
|||
QtAndroid::startActivity(chooserIntent, |
|||
_flags.testFlag(PersistPermissionsFlag) ? RequestCodePersist : RequestCodeNormal, |
|||
this); |
|||
} |
|||
|
|||
void AndroidFileChooser::setTitle(const QString &title) |
|||
{ |
|||
if (_title == title) |
|||
return; |
|||
|
|||
_title = title; |
|||
emit titleChanged(title); |
|||
} |
|||
|
|||
void AndroidFileChooser::setFolderUrl(const QUrl &contentUrl) |
|||
{ |
|||
if (_folderUrl == contentUrl) |
|||
return; |
|||
|
|||
_folderUrl = contentUrl; |
|||
emit folderUrlChanged(contentUrl); |
|||
} |
|||
|
|||
void AndroidFileChooser::setType(AndroidFileChooser::ChooserType type) |
|||
{ |
|||
if (_type == type) |
|||
return; |
|||
|
|||
_type = type; |
|||
emit typeChanged(type); |
|||
} |
|||
|
|||
void AndroidFileChooser::setMimeTypes(const QStringList &mimeType) |
|||
{ |
|||
if (_mimeTypes == mimeType) |
|||
return; |
|||
|
|||
_mimeTypes = mimeType; |
|||
emit mimeTypesChanged(mimeType); |
|||
} |
|||
|
|||
void AndroidFileChooser::setChooserFlags(ChooserFlags chooserFlags) |
|||
{ |
|||
if (_flags == chooserFlags) |
|||
return; |
|||
|
|||
_flags = chooserFlags; |
|||
emit chooserFlagsChanged(chooserFlags); |
|||
} |
|||
|
|||
void AndroidFileChooser::handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data) |
|||
{ |
|||
if(receiverRequestCode == RequestCodeNormal || receiverRequestCode == RequestCodePersist) { |
|||
static const auto RESULT_OK = QAndroidJniObject::getStaticField<jint>("android/app/Activity", "RESULT_OK"); |
|||
static const auto FLAG_GRANT_READ_URI_PERMISSION = QAndroidJniObject::getStaticField<jint>("android/content/Intent", "FLAG_GRANT_READ_URI_PERMISSION"); |
|||
static const auto FLAG_GRANT_WRITE_URI_PERMISSION = QAndroidJniObject::getStaticField<jint>("android/content/Intent", "FLAG_GRANT_WRITE_URI_PERMISSION"); |
|||
|
|||
if(resultCode == RESULT_OK) { |
|||
if(receiverRequestCode == RequestCodePersist) { |
|||
auto flags = data.callMethod<jint>("getFlags"); |
|||
flags &= (FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION); |
|||
|
|||
auto resolver = QtAndroid::androidContext().callObjectMethod("getContentResolver", "()Landroid/content/ContentResolver;"); |
|||
resolver.callMethod<void>("takePersistableUriPermission", "(Landroid/net/Uri;I)V", |
|||
data.callObjectMethod("getData", "()Landroid/net/Uri;").object(), |
|||
flags); |
|||
} |
|||
|
|||
_result.clear(); |
|||
if(_type == OpenMultipleDocuments) { |
|||
auto clipData = data.callObjectMethod("getClipData", "()Landroid/content/ClipData;"); |
|||
if(clipData.isValid()) { |
|||
QList<QUrl> urls; |
|||
const auto cnt = clipData.callMethod<jint>("getItemCount"); |
|||
for(auto i = 0; i < cnt; i++) { |
|||
auto item = clipData.callObjectMethod("getItemAt", "(I)Landroid/content/ClipData$Item;", i); |
|||
urls.append(item.callObjectMethod("getUri", "()Landroid/net/Uri;").toString()); |
|||
} |
|||
_result = QVariant::fromValue(urls); |
|||
} |
|||
} |
|||
|
|||
if(!_result.isValid()) |
|||
_result = QUrl(data.callObjectMethod("getDataString", "()Ljava/lang/String;").toString()); |
|||
emit resultChanged(_result); |
|||
_active = false; |
|||
emit accepted(); |
|||
} else { |
|||
_active = false; |
|||
emit rejected(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
QAndroidJniObject AndroidFileChooser::createGetIntent() |
|||
{ |
|||
static const auto ACTION_GET_CONTENT = QAndroidJniObject::getStaticObjectField<jstring>("android/content/Intent", "ACTION_GET_CONTENT"); |
|||
static const auto EXTRA_LOCAL_ONLY = QAndroidJniObject::getStaticObjectField<jstring>("android/content/Intent", "EXTRA_LOCAL_ONLY"); |
|||
|
|||
QAndroidJniObject intent("android/content/Intent", "(Ljava/lang/String;)V", |
|||
ACTION_GET_CONTENT.object()); |
|||
setupBasic(intent); |
|||
|
|||
if(_flags.testFlag(LocalOnlyFlag)) { |
|||
intent.callObjectMethod("putExtra", "(Ljava/lang/String;Z)Landroid/content/Intent;", |
|||
EXTRA_LOCAL_ONLY.object(), true); |
|||
} |
|||
|
|||
return intent; |
|||
} |
|||
|
|||
QAndroidJniObject AndroidFileChooser::createOpenIntent() |
|||
{ |
|||
static const auto ACTION_OPEN_DOCUMENT = QAndroidJniObject::getStaticObjectField<jstring>("android/content/Intent", "ACTION_OPEN_DOCUMENT"); |
|||
|
|||
QAndroidJniObject intent("android/content/Intent", "(Ljava/lang/String;)V", |
|||
ACTION_OPEN_DOCUMENT.object()); |
|||
setupBasic(intent); |
|||
|
|||
return intent; |
|||
} |
|||
|
|||
QAndroidJniObject AndroidFileChooser::createOpenMultipleIntent() |
|||
{ |
|||
static const auto EXTRA_ALLOW_MULTIPLE = QAndroidJniObject::getStaticObjectField<jstring>("android/content/Intent", "EXTRA_ALLOW_MULTIPLE"); |
|||
|
|||
auto intent = createOpenIntent(); |
|||
intent.callObjectMethod("putExtra", "(Ljava/lang/String;Z)Landroid/content/Intent;", |
|||
EXTRA_ALLOW_MULTIPLE.object(), true); |
|||
|
|||
return intent; |
|||
} |
|||
|
|||
QAndroidJniObject AndroidFileChooser::createSaveIntent() |
|||
{ |
|||
static const auto ACTION_CREATE_DOCUMENT = QAndroidJniObject::getStaticObjectField<jstring>("android/content/Intent", "ACTION_CREATE_DOCUMENT"); |
|||
|
|||
QAndroidJniObject intent("android/content/Intent", "(Ljava/lang/String;)V", |
|||
ACTION_CREATE_DOCUMENT.object()); |
|||
setupBasic(intent); |
|||
|
|||
return intent; |
|||
} |
|||
|
|||
QAndroidJniObject AndroidFileChooser::createOpenTreeIntent() |
|||
{ |
|||
static const auto ACTION_OPEN_DOCUMENT_TREE = QAndroidJniObject::getStaticObjectField<jstring>("android/content/Intent", "ACTION_OPEN_DOCUMENT_TREE"); |
|||
|
|||
QAndroidJniObject intent("android/content/Intent", "(Ljava/lang/String;)V", |
|||
ACTION_OPEN_DOCUMENT_TREE.object()); |
|||
setupBasic(intent, true); |
|||
|
|||
return intent; |
|||
} |
|||
|
|||
void AndroidFileChooser::setupBasic(QAndroidJniObject &intent, bool asTree) |
|||
{ |
|||
static const auto CATEGORY_OPENABLE = QAndroidJniObject::getStaticObjectField<jstring>("android/content/Intent", "CATEGORY_OPENABLE"); |
|||
|
|||
static const auto FLAG_GRANT_PERSISTABLE_URI_PERMISSION = QAndroidJniObject::getStaticField<jint>("android/content/Intent", "FLAG_GRANT_PERSISTABLE_URI_PERMISSION"); |
|||
static const auto FLAG_GRANT_READ_URI_PERMISSION = QAndroidJniObject::getStaticField<jint>("android/content/Intent", "FLAG_GRANT_READ_URI_PERMISSION"); |
|||
static const auto FLAG_GRANT_WRITE_URI_PERMISSION = QAndroidJniObject::getStaticField<jint>("android/content/Intent", "FLAG_GRANT_WRITE_URI_PERMISSION"); |
|||
|
|||
static const auto EXTRA_MIME_TYPES = QAndroidJniObject::getStaticObjectField<jstring>("android/content/Intent", "EXTRA_MIME_TYPES"); |
|||
static const auto EXTRA_INITIAL_URI = [](){ |
|||
if(QtAndroid::androidSdkVersion() >= 26) //Android Oreo
|
|||
return QAndroidJniObject::getStaticObjectField<jstring>("android/content/Intent", "EXTRA_INITIAL_URI"); |
|||
else |
|||
return QAndroidJniObject(); |
|||
}(); |
|||
|
|||
//set the acceptable mimetypes
|
|||
if(!asTree) { |
|||
if(_mimeTypes.size() == 1) { |
|||
intent.callObjectMethod("setTypeAndNormalize", "(Ljava/lang/String;)Landroid/content/Intent;", |
|||
QAndroidJniObject::fromString(_mimeTypes.first()).object()); |
|||
} else { |
|||
intent.callObjectMethod("setType", "(Ljava/lang/String;)Landroid/content/Intent;", |
|||
QAndroidJniObject::fromString(QStringLiteral("*/*")).object()); |
|||
|
|||
if(!_mimeTypes.isEmpty()) { |
|||
QAndroidJniEnvironment env; |
|||
auto strClass = env->FindClass("java/lang/String"); |
|||
QAndroidJniObject strArray(env->NewObjectArray(_mimeTypes.size(), strClass, nullptr)); |
|||
for(auto i = 0; i < _mimeTypes.size(); i++) { |
|||
auto mimeStr = QAndroidJniObject::callStaticObjectMethod("android/content/Intent", "normalizeMimeType", |
|||
"(Ljava/lang/String;)Ljava/lang/String;", |
|||
QAndroidJniObject::fromString(_mimeTypes[i]).object()); |
|||
env->SetObjectArrayElement(strArray.object<jobjectArray>(), i, mimeStr.object()); |
|||
} |
|||
|
|||
intent.callObjectMethod("putExtra", "(Ljava/lang/String;[Ljava/lang/String;)Landroid/content/Intent;", |
|||
EXTRA_MIME_TYPES.object(), strArray.object()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
//Set the intent flags
|
|||
auto aFlags = FLAG_GRANT_READ_URI_PERMISSION; |
|||
if(_type != GetContent) |
|||
aFlags |= FLAG_GRANT_PERSISTABLE_URI_PERMISSION; |
|||
if(_type == CreateDocument || _flags.testFlag(AlwaysGrantWriteFlag)) |
|||
aFlags |= FLAG_GRANT_WRITE_URI_PERMISSION; |
|||
intent.callObjectMethod("addFlags", "(I)Landroid/content/Intent;", |
|||
aFlags); |
|||
|
|||
//set openable
|
|||
if(_flags.testFlag(OpenableFlag) && !asTree) { |
|||
intent.callObjectMethod("addCategory", "(Ljava/lang/String;)Landroid/content/Intent;", |
|||
CATEGORY_OPENABLE.object()); |
|||
} |
|||
|
|||
|
|||
if(EXTRA_INITIAL_URI.isValid()) { |
|||
auto uri = QAndroidJniObject::callStaticObjectMethod("android/net/Uri", "parse", |
|||
"(Ljava/lang/String;)Landroid/net/Uri;", |
|||
QAndroidJniObject::fromString(_folderUrl.toString()).object()); |
|||
intent.callObjectMethod("putExtra", "(Ljava/lang/String;Z)Landroid/content/Intent;", |
|||
EXTRA_INITIAL_URI.object(), uri.object()); |
|||
} |
|||
} |
@ -0,0 +1,106 @@ |
|||
#ifndef QTMVVM_ANDROIDFILECHOOSER_H |
|||
#define QTMVVM_ANDROIDFILECHOOSER_H |
|||
|
|||
#include <QtCore/QObject> |
|||
#include <QtCore/QUrl> |
|||
#include <QtCore/QVariant> |
|||
|
|||
#include <QtAndroidExtras/QAndroidActivityResultReceiver> |
|||
#include <QtAndroidExtras/QAndroidJniObject> |
|||
|
|||
namespace QtMvvm { |
|||
|
|||
class AndroidFileChooser : public QObject, public QAndroidActivityResultReceiver |
|||
{ |
|||
Q_OBJECT |
|||
|
|||
Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged) |
|||
|
|||
Q_PROPERTY(QUrl folderUrl READ folderUrl WRITE setFolderUrl NOTIFY folderUrlChanged) |
|||
Q_PROPERTY(ChooserType type READ type WRITE setType NOTIFY typeChanged) |
|||
Q_PROPERTY(QStringList mimeTypes READ mimeTypes WRITE setMimeTypes NOTIFY mimeTypesChanged) |
|||
Q_PROPERTY(ChooserFlags chooserFlags READ chooserFlags WRITE setChooserFlags NOTIFY chooserFlagsChanged) |
|||
|
|||
Q_PROPERTY(QVariant result READ result NOTIFY resultChanged) |
|||
|
|||
public: |
|||
enum ChooserType { |
|||
GetContent = 0, |
|||
OpenDocument = 1, |
|||
OpenMultipleDocuments = 2, |
|||
CreateDocument = 3, |
|||
OpenDocumentTree = 4 |
|||
}; |
|||
Q_ENUM(ChooserType) |
|||
|
|||
enum ChooserFlag { |
|||
OpenableFlag = 0x01, |
|||
LocalOnlyFlag = 0x02, |
|||
AlwaysGrantWriteFlag = 0x04, |
|||
PersistPermissionsFlag = 0x08 |
|||
}; |
|||
Q_DECLARE_FLAGS(ChooserFlags, ChooserFlag) |
|||
Q_FLAG(ChooserFlags) |
|||
|
|||
explicit AndroidFileChooser(QObject *parent = nullptr); |
|||
~AndroidFileChooser(); |
|||
|
|||
QString title() const; |
|||
QUrl folderUrl() const; |
|||
ChooserType type() const; |
|||
QStringList mimeTypes() const; |
|||
ChooserFlags chooserFlags() const; |
|||
|
|||
QVariant result() const; |
|||
|
|||
public Q_SLOTS: |
|||
void open(); |
|||
|
|||
void setTitle(const QString &title); |
|||
void setFolderUrl(const QUrl &folderUrl); |
|||
void setType(ChooserType type); |
|||
void setMimeTypes(const QStringList &mimeTypes); |
|||
void setChooserFlags(ChooserFlags chooserFlags); |
|||
|
|||
Q_SIGNALS: |
|||
void accepted(); |
|||
void rejected(); |
|||
|
|||
void titleChanged(const QString &title); |
|||
void folderUrlChanged(const QUrl &folderUrl); |
|||
void typeChanged(ChooserType type); |
|||
void mimeTypesChanged(const QStringList &mimeTypes); |
|||
void chooserFlagsChanged(ChooserFlags chooserFlags); |
|||
|
|||
void resultChanged(QVariant result); |
|||
|
|||
protected: |
|||
void handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data) override; |
|||
|
|||
private: |
|||
const static int RequestCodeNormal = 0x1091c657; |
|||
const static int RequestCodePersist = 0x1091c658; |
|||
|
|||
QString _title; |
|||
QUrl _folderUrl; |
|||
ChooserType _type; |
|||
QStringList _mimeTypes; |
|||
ChooserFlags _flags; |
|||
|
|||
bool _active; |
|||
QVariant _result; |
|||
|
|||
QAndroidJniObject createGetIntent(); |
|||
QAndroidJniObject createOpenIntent(); |
|||
QAndroidJniObject createOpenMultipleIntent(); |
|||
QAndroidJniObject createSaveIntent(); |
|||
QAndroidJniObject createOpenTreeIntent(); |
|||
|
|||
void setupBasic(QAndroidJniObject &intent, bool asTree = false); |
|||
}; |
|||
|
|||
} |
|||
|
|||
Q_DECLARE_OPERATORS_FOR_FLAGS(QtMvvm::AndroidFileChooser::ChooserFlags) |
|||
|
|||
#endif // QTMVVM_ANDROIDFILECHOOSER_H
|
Loading…
Reference in new issue