Browse Source

implemented file dialogs with android support

pull/2/head
Skycoder42 7 years ago
parent
commit
12a4b9af9e
  1. 2
      examples/mvvmquick/SampleQuick/SampleQuick.pro
  2. 12
      examples/mvvmquick/SampleQuick/SampleView.qml
  3. 5
      examples/mvvmquick/SampleQuick/main.cpp
  4. 14
      examples/mvvmwidgets/SampleWidgets/SampleWidgets.pro
  5. 43
      src/imports/mvvmquick/AndroidFileDialog.qml
  6. 31
      src/imports/mvvmquick/AndroidFolderDialog.qml
  7. 3
      src/imports/mvvmquick/FileDialog.qml
  8. 304
      src/imports/mvvmquick/androidfilechooser.cpp
  9. 106
      src/imports/mvvmquick/androidfilechooser.h
  10. 6
      src/imports/mvvmquick/mvvmquick.pro
  11. 7
      src/imports/mvvmquick/qtmvvmquick_plugin.cpp
  12. 2
      src/src.pro

2
examples/mvvmquick/SampleQuick/SampleQuick.pro

@ -13,7 +13,7 @@ target.path = $$[QT_INSTALL_EXAMPLES]/mvvmquick/$$TARGET
INSTALLS += target
#not found by linker?
unix:!mac {
linux:!android {
LIBS += -L$$OUT_PWD/../../../lib #required to make this the first place to search
LIBS += -L$$[QT_INSTALL_LIBS] -licudata
LIBS += -L$$[QT_INSTALL_LIBS] -licui18n

12
examples/mvvmquick/SampleQuick/SampleView.qml

@ -15,19 +15,19 @@ Page {
title: qsTr("Sample")
moreMenu: Menu {
Action {
text: qsTr("Another &Input")
MenuItem {
text: qsTr("Another Input")
onTriggered: viewModel.getInput()
}
Action {
text: qsTr("Add &Files")
MenuItem {
text: qsTr("Add Files")
onTriggered: viewModel.getFiles()
}
MenuSeparator {}
Action {
text: qsTr("&About")
MenuItem {
text: qsTr("About")
onTriggered: viewModel.about()
}
}

5
examples/mvvmquick/SampleQuick/main.cpp

@ -1,7 +1,9 @@
#include <QApplication>
#include <QtWidgets/QApplication>
#include <QQmlApplicationEngine>
#include <QtMvvmCore/ServiceRegistry>
#include <QtMvvmQuick/QuickPresenter>
#include <QtQuickControls2/QQuickStyle>
#include <QtCore/QDebug>
#include <quickextras.h>
@ -23,6 +25,7 @@ int main(int argc, char *argv[])
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
qDebug() << QQuickStyle::availableStyles() << QQuickStyle::name();
qmlRegisterUncreatableType<SampleViewModel>("de.skycoder42.QtMvvm.Sample", 1, 0, "SampleViewModel", QStringLiteral("ViewModels cannot be created"));
qmlRegisterUncreatableType<ResultViewModel>("de.skycoder42.QtMvvm.Sample", 1, 0, "ResultViewModel", QStringLiteral("ViewModels cannot be created"));

14
examples/mvvmwidgets/SampleWidgets/SampleWidgets.pro

@ -6,24 +6,24 @@ TARGET = SampleWidgets
HEADERS += \
widgetseventservice.h \
sampleview.h \
resultdialog.h
sampleview.h \
resultdialog.h
SOURCES += \
main.cpp \
widgetseventservice.cpp \
sampleview.cpp \
resultdialog.cpp
sampleview.cpp \
resultdialog.cpp
FORMS += \
sampleview.ui \
resultdialog.ui
sampleview.ui \
resultdialog.ui
target.path = $$[QT_INSTALL_EXAMPLES]/mvvmwidgets/$$TARGET
INSTALLS += target
#not found by linker?
unix:!mac {
linux:!android {
LIBS += -L$$OUT_PWD/../../../lib #required to make this the first place to search
LIBS += -L$$[QT_INSTALL_LIBS] -licudata
LIBS += -L$$[QT_INSTALL_LIBS] -licui18n

43
src/imports/mvvmquick/AndroidFileDialog.qml

@ -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();
}
}

31
src/imports/mvvmquick/AndroidFolderDialog.qml

@ -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();
}
}

3
src/imports/mvvmquick/FileDialog.qml

@ -9,6 +9,7 @@ Labs.FileDialog {
property var msgConfig
property MessageResult msgResult
property var mimeTypes: []
signal closed()
@ -26,7 +27,7 @@ Labs.FileDialog {
return Labs.FileDialog.OpenFile; //fallback
}
}
nameFilters: QuickPresenter.mimeTypeFilters(msgConfig.viewProperties["mimeTypes"])
nameFilters: QuickPresenter.mimeTypeFilters(mimeTypes)
Component.onCompleted: {
if(msgResult)

304
src/imports/mvvmquick/androidfilechooser.cpp

@ -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());
}
}

106
src/imports/mvvmquick/androidfilechooser.h

@ -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

6
src/imports/mvvmquick/mvvmquick.pro

@ -29,6 +29,12 @@ QML_FILES += \
OTHER_FILES += qmldir
android {
QT += androidextras
HEADERS += androidfilechooser.h
SOURCES += androidfilechooser.cpp
}
generate_qmltypes {
typeextra1.target = qmltypes
typeextra1.depends += export LD_LIBRARY_PATH := "$$shadowed($$dirname(_QMAKE_CONF_))/lib/:$$[QT_INSTALL_LIBS]:$(LD_LIBRARY_PATH)"

7
src/imports/mvvmquick/qtmvvmquick_plugin.cpp

@ -6,6 +6,9 @@
#include "qqmlquickpresenter.h"
#include "svgimageprovider.h"
#ifdef Q_OS_ANDROID
#include "androidfilechooser.h"
#endif
static void initResources()
{
@ -39,6 +42,10 @@ void QtMvvmQuickDeclarativeModule::registerTypes(const char *uri)
qmlRegisterType(QUrl(QStringLiteral("qrc:/de/skycoder42/qtmvvm/quick/qml/FileDialog.qml")), uri, 1, 0, "FileDialog");
qmlRegisterType(QUrl(QStringLiteral("qrc:/de/skycoder42/qtmvvm/quick/qml/FolderDialog.qml")), uri, 1, 0, "FolderDialog");
#ifdef Q_OS_ANDROID
qmlRegisterType<QtMvvm::AndroidFileChooser>(uri, 1, 0, "FileChooser");
#endif
// Check to make shure no module update is forgotten
static_assert(VERSION_MAJOR == 1 && VERSION_MINOR == 0, "QML module version needs to be updated");
}

2
src/src.pro

@ -4,7 +4,7 @@ CONFIG += ordered
SUBDIRS += mvvmcore \
mvvmwidgets \
mvvmquick \
imports
imports
prepareRecursiveTarget(lrelease)
QMAKE_EXTRA_TARGETS += lrelease

Loading…
Cancel
Save