WIP: Show native file dialog under Android
This commit is contained in:
parent
fc1f6e7b91
commit
d3b95825df
|
@ -71,6 +71,14 @@ set(QML_SRC_FILES
|
||||||
resources/icons.qrc
|
resources/icons.qrc
|
||||||
resources/qml.qrc
|
resources/qml.qrc
|
||||||
)
|
)
|
||||||
|
if(ANDROID)
|
||||||
|
list(APPEND QML_HEADER_FILES
|
||||||
|
quickgui/android.h
|
||||||
|
)
|
||||||
|
list(APPEND QML_SRC_FILES
|
||||||
|
quickgui/android.cpp
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(TS_FILES
|
set(TS_FILES
|
||||||
translations/${META_PROJECT_NAME}_de_DE.ts
|
translations/${META_PROJECT_NAME}_de_DE.ts
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<application android:icon="@drawable/icon" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="@string/app_name">
|
<application android:icon="@drawable/icon" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="@string/app_name">
|
||||||
<activity
|
<activity
|
||||||
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation"
|
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation"
|
||||||
android:name="org.qtproject.qt5.android.bindings.QtActivity"
|
android:name="org.martchus.passwordmanager.Activity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:screenOrientation="unspecified"
|
android:screenOrientation="unspecified"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
<!-- Splash screen -->
|
<!-- Splash screen -->
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="21"/>
|
<uses-sdk android:minSdkVersion="23" android:targetSdkVersion="23"/>
|
||||||
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
|
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
|
||||||
|
|
||||||
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
|
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:2.2.3'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
maven {
|
||||||
|
url 'https://maven.google.com'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
compile 'com.android.support:support-v4:27.1.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
/*******************************************************
|
||||||
|
* The following variables:
|
||||||
|
* - androidBuildToolsVersion,
|
||||||
|
* - androidCompileSdkVersion
|
||||||
|
* - qt5AndroidDir - holds the path to qt android files
|
||||||
|
* needed to build any Qt application
|
||||||
|
* on Android.
|
||||||
|
*
|
||||||
|
* are defined in gradle.properties file. This file is
|
||||||
|
* updated by QtCreator and androiddeployqt tools.
|
||||||
|
* Changing them manually might break the compilation!
|
||||||
|
*******************************************************/
|
||||||
|
|
||||||
|
compileSdkVersion androidCompileSdkVersion.toInteger()
|
||||||
|
|
||||||
|
buildToolsVersion androidBuildToolsVersion
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
manifest.srcFile 'AndroidManifest.xml'
|
||||||
|
java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
|
||||||
|
aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
|
||||||
|
res.srcDirs = [qt5AndroidDir + '/res', 'res']
|
||||||
|
resources.srcDirs = ['src']
|
||||||
|
renderscript.srcDirs = ['src']
|
||||||
|
assets.srcDirs = ['assets']
|
||||||
|
jniLibs.srcDirs = ['libs']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lintOptions {
|
||||||
|
abortOnError false
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package org.martchus.passwordmanager;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.support.v4.provider.DocumentFile;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import org.qtproject.qt5.android.bindings.QtActivity;
|
||||||
|
|
||||||
|
public class Activity extends QtActivity {
|
||||||
|
private final int REQUEST_CODE_PICK_DIR = 1;
|
||||||
|
private final int REQUEST_CODE_PICK_EXISTING_FILE = 2;
|
||||||
|
private final int REQUEST_CODE_PICK_NEW_FILE = 3;
|
||||||
|
|
||||||
|
public boolean showAndroidFileDialog(boolean existing) {
|
||||||
|
String action = existing ? Intent.ACTION_OPEN_DOCUMENT : Intent.ACTION_CREATE_DOCUMENT;
|
||||||
|
int requestCode = existing ? REQUEST_CODE_PICK_EXISTING_FILE : REQUEST_CODE_PICK_NEW_FILE;
|
||||||
|
Intent intent = new Intent(action);
|
||||||
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
intent.setType("*/*");
|
||||||
|
try {
|
||||||
|
startActivityForResult(intent, requestCode);
|
||||||
|
return true;
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
switch (requestCode) {
|
||||||
|
case REQUEST_CODE_PICK_EXISTING_FILE:
|
||||||
|
case REQUEST_CODE_PICK_NEW_FILE:
|
||||||
|
if (resultCode != RESULT_OK) {
|
||||||
|
onAndroidFileDialogRejected();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean existingFile = requestCode == REQUEST_CODE_PICK_EXISTING_FILE;
|
||||||
|
Uri uri = data.getData();
|
||||||
|
if (uri != null) {
|
||||||
|
try {
|
||||||
|
DocumentFile file = DocumentFile.fromSingleUri(this, uri);
|
||||||
|
ParcelFileDescriptor fd = getContentResolver().openFileDescriptor(file.getUri(), existingFile ? "rw" : "wt");
|
||||||
|
onAndroidFileDialogAcceptedHandle(uri.toString(), fd.getFd(), existingFile);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
onAndroidError("Failed to open selected file.");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String fileName = data.getDataString();
|
||||||
|
if (fileName != null) {
|
||||||
|
onAndroidFileDialogAccepted(fileName, existingFile);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onAndroidError("Failed to read result from Android's file dialog.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static native void onAndroidError(String message);
|
||||||
|
public static native void onAndroidFileDialogAccepted(String fileName, boolean existing);
|
||||||
|
public static native void onAndroidFileDialogAcceptedHandle(String fileName, int fileHandle, boolean existing);
|
||||||
|
public static native void onAndroidFileDialogRejected();
|
||||||
|
}
|
43
qml/main.qml
43
qml/main.qml
|
@ -71,6 +71,12 @@ Kirigami.ApplicationWindow {
|
||||||
onTriggered: nativeInterface.close()
|
onTriggered: nativeInterface.close()
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
Controls.Switch {
|
||||||
|
text: qsTr("Use native file dialog")
|
||||||
|
checked: nativeInterface.useNativeFileDialog
|
||||||
|
visible: nativeInterface.supportsNativeFileDialog
|
||||||
|
onCheckedChanged: nativeInterface.useNativeFileDialog = checked
|
||||||
|
}
|
||||||
Controls.Switch {
|
Controls.Switch {
|
||||||
id: showPasswordsOnFocusSwitch
|
id: showPasswordsOnFocusSwitch
|
||||||
text: qsTr("Show passwords on focus")
|
text: qsTr("Show passwords on focus")
|
||||||
|
@ -97,27 +103,31 @@ Kirigami.ApplicationWindow {
|
||||||
id: fileDialog
|
id: fileDialog
|
||||||
title: selectExisting ? qsTr("Select an existing file") : qsTr(
|
title: selectExisting ? qsTr("Select an existing file") : qsTr(
|
||||||
"Select path for new file")
|
"Select path for new file")
|
||||||
|
property bool selectExisting: true
|
||||||
|
|
||||||
onAccepted: {
|
onAccepted: {
|
||||||
if (fileUrls.length < 1) {
|
if (fileUrls.length < 1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (selectExisting) {
|
nativeInterface.handleFileSelectionAccepted(fileUrls[0],
|
||||||
nativeInterface.load(fileUrls[0])
|
this.selectExisting)
|
||||||
} else {
|
|
||||||
nativeInterface.create(fileUrls[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onRejected: {
|
|
||||||
showPassiveNotification(qsTr("Canceled file selection"))
|
|
||||||
}
|
}
|
||||||
|
onRejected: nativeInterface.handleFileSelectionCanceled()
|
||||||
|
|
||||||
|
function show() {
|
||||||
|
if (nativeInterface.showNativeFileDialog(this.selectExisting)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// fallback to the Qt Quick file dialog if a native implementation is not available
|
||||||
|
this.open()
|
||||||
|
}
|
||||||
function openExisting() {
|
function openExisting() {
|
||||||
this.selectExisting = true
|
this.selectExisting = true
|
||||||
this.open()
|
this.show()
|
||||||
}
|
}
|
||||||
function createNew() {
|
function createNew() {
|
||||||
this.selectExisting = false
|
this.selectExisting = false
|
||||||
this.open()
|
this.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,6 +159,9 @@ Kirigami.ApplicationWindow {
|
||||||
showPassiveNotification(qsTr("%1 saved").arg(
|
showPassiveNotification(qsTr("%1 saved").arg(
|
||||||
nativeInterface.fileName))
|
nativeInterface.fileName))
|
||||||
}
|
}
|
||||||
|
onNewNotification: {
|
||||||
|
showPassiveNotification(message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
|
@ -173,17 +186,17 @@ Kirigami.ApplicationWindow {
|
||||||
|
|
||||||
function pushStackEntry(entryModel, rootIndex) {
|
function pushStackEntry(entryModel, rootIndex) {
|
||||||
pageStack.push(entriesComponent.createObject(root, {
|
pageStack.push(entriesComponent.createObject(root, {
|
||||||
entryModel: entryModel,
|
"entryModel": entryModel,
|
||||||
rootIndex: rootIndex,
|
"rootIndex": rootIndex,
|
||||||
title: entryModel.data(
|
"title": entryModel.data(
|
||||||
rootIndex)
|
rootIndex)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
function createFileActions(files) {
|
function createFileActions(files) {
|
||||||
return files.map(function (filePath) {
|
return files.map(function (filePath) {
|
||||||
return this.createObject(root, {
|
return this.createObject(root, {
|
||||||
filePath: filePath
|
"filePath": filePath
|
||||||
})
|
})
|
||||||
}, fileActionComponent)
|
}, fileActionComponent)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
#include "./android.h"
|
||||||
|
#include "./controller.h"
|
||||||
|
|
||||||
|
#include <QtAndroid>
|
||||||
|
#include <QAndroidJniObject>
|
||||||
|
#include <QMetaObject>
|
||||||
|
|
||||||
|
#include <jni.h>
|
||||||
|
|
||||||
|
namespace QtGui {
|
||||||
|
|
||||||
|
static Controller *controllerForAndroid = nullptr;
|
||||||
|
|
||||||
|
void registerControllerForAndroid(Controller *controller)
|
||||||
|
{
|
||||||
|
controllerForAndroid = controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool showAndroidFileDialog(bool existing)
|
||||||
|
{
|
||||||
|
return QtAndroid::androidActivity().callMethod<jboolean>("showAndroidFileDialog", "(Z)Z", existing);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void onAndroidError(JNIEnv *, jobject, jstring message)
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(QtGui::controllerForAndroid, "newNotification", Qt::QueuedConnection, Q_ARG(QString, QAndroidJniObject::fromLocalRef(message).toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void onAndroidFileDialogAccepted(JNIEnv *, jobject, jstring fileName, jboolean existing)
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(QtGui::controllerForAndroid, "handleFileSelectionAccepted", Qt::QueuedConnection, Q_ARG(QString, QAndroidJniObject::fromLocalRef(fileName).toString()), Q_ARG(bool, existing));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void onAndroidFileDialogAcceptedHandle(JNIEnv *, jobject, jstring fileName, jint fileHandle, jboolean existing)
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(QtGui::controllerForAndroid, "handleFileSelectionAcceptedDescriptor", Qt::QueuedConnection, Q_ARG(QString, QAndroidJniObject::fromLocalRef(fileName).toString()), Q_ARG(int, fileHandle), Q_ARG(bool, existing));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void onAndroidFileDialogRejected(JNIEnv *, jobject)
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(QtGui::controllerForAndroid, "handleFileSelectionCanceled", Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Registers the static functions declared above so they can be called from the Java-side.
|
||||||
|
* \remarks This method is called automatically by Java after the .so file is loaded.
|
||||||
|
*/
|
||||||
|
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *)
|
||||||
|
{
|
||||||
|
// get the JNIEnv pointer
|
||||||
|
JNIEnv *env;
|
||||||
|
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
|
||||||
|
return JNI_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// search for Java class which declares the native methods
|
||||||
|
const auto javaClass = env->FindClass("org/martchus/passwordmanager/Activity");
|
||||||
|
if (!javaClass) {
|
||||||
|
return JNI_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// register native methods
|
||||||
|
static const JNINativeMethod methods[] = {
|
||||||
|
{"onAndroidError", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onAndroidError)},
|
||||||
|
{"onAndroidFileDialogAccepted", "(Ljava/lang/String;Z)V", reinterpret_cast<void *>(onAndroidFileDialogAccepted)},
|
||||||
|
{"onAndroidFileDialogAcceptedHandle", "(Ljava/lang/String;IZ)V", reinterpret_cast<void *>(onAndroidFileDialogAcceptedHandle)},
|
||||||
|
{"onAndroidFileDialogRejected", "()V", reinterpret_cast<void *>(onAndroidFileDialogRejected)},
|
||||||
|
};
|
||||||
|
if (env->RegisterNatives(javaClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
|
||||||
|
return JNI_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JNI_VERSION_1_6;
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
#ifndef QT_QUICK_GUI_ANDROID_H
|
||||||
|
#define QT_QUICK_GUI_ANDROID_H
|
||||||
|
|
||||||
|
namespace QtGui {
|
||||||
|
|
||||||
|
class Controller;
|
||||||
|
void registerControllerForAndroid(Controller *controller);
|
||||||
|
bool showAndroidFileDialog(bool existing);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // QT_QUICK_GUI_ANDROID_H
|
|
@ -1,4 +1,5 @@
|
||||||
#include "./controller.h"
|
#include "./controller.h"
|
||||||
|
#include "./android.h"
|
||||||
|
|
||||||
#include <passwordfile/io/cryptoexception.h>
|
#include <passwordfile/io/cryptoexception.h>
|
||||||
#include <passwordfile/io/parsingexception.h>
|
#include <passwordfile/io/parsingexception.h>
|
||||||
|
@ -6,6 +7,7 @@
|
||||||
#include <qtutilities/misc/dialogutils.h>
|
#include <qtutilities/misc/dialogutils.h>
|
||||||
|
|
||||||
#include <c++utilities/io/catchiofailure.h>
|
#include <c++utilities/io/catchiofailure.h>
|
||||||
|
#include <c++utilities/io/nativefilestream.h>
|
||||||
#include <c++utilities/io/path.h>
|
#include <c++utilities/io/path.h>
|
||||||
|
|
||||||
#ifndef QT_NO_CLIPBOARD
|
#ifndef QT_NO_CLIPBOARD
|
||||||
|
@ -32,12 +34,14 @@ Controller::Controller(QSettings &settings, const QString &filePath, QObject *pa
|
||||||
, m_settings(settings)
|
, m_settings(settings)
|
||||||
, m_fileOpen(false)
|
, m_fileOpen(false)
|
||||||
, m_fileModified(false)
|
, m_fileModified(false)
|
||||||
|
, m_useNativeFileDialog(false)
|
||||||
{
|
{
|
||||||
m_entryFilterModel.setSourceModel(&m_entryModel);
|
m_entryFilterModel.setSourceModel(&m_entryModel);
|
||||||
|
|
||||||
// share settings with main window
|
// share settings with main window
|
||||||
m_settings.beginGroup(QStringLiteral("mainwindow"));
|
m_settings.beginGroup(QStringLiteral("mainwindow"));
|
||||||
m_recentFiles = m_settings.value(QStringLiteral("recententries")).toStringList();
|
m_recentFiles = m_settings.value(QStringLiteral("recententries")).toStringList();
|
||||||
|
m_useNativeFileDialog = m_settings.value(QStringLiteral("usenativefiledialog"), m_useNativeFileDialog).toBool();
|
||||||
|
|
||||||
// set initial file path
|
// set initial file path
|
||||||
setFilePath(filePath);
|
setFilePath(filePath);
|
||||||
|
@ -66,6 +70,11 @@ void Controller::setFilePath(const QString &filePath)
|
||||||
emit filePathChanged(m_filePath = filePath);
|
emit filePathChanged(m_filePath = filePath);
|
||||||
|
|
||||||
// handle recent files
|
// handle recent files
|
||||||
|
#ifdef Q_OS_ANDROID
|
||||||
|
if (m_useNativeFileDialog) {
|
||||||
|
return; // native file dialog under Android makes it impossible to store URIs persistently
|
||||||
|
}
|
||||||
|
#endif
|
||||||
auto index = m_recentFiles.indexOf(m_filePath);
|
auto index = m_recentFiles.indexOf(m_filePath);
|
||||||
if (!index) {
|
if (!index) {
|
||||||
return;
|
return;
|
||||||
|
@ -131,7 +140,10 @@ void Controller::create(const QString &filePath)
|
||||||
|
|
||||||
resetFileStatus();
|
resetFileStatus();
|
||||||
try {
|
try {
|
||||||
m_file.create();
|
if (!m_file.isOpen()) {
|
||||||
|
m_file.create();
|
||||||
|
}
|
||||||
|
m_file.generateRootEntry();
|
||||||
m_entryModel.setRootEntry(m_file.rootEntry());
|
m_entryModel.setRootEntry(m_file.rootEntry());
|
||||||
setFileOpen(true);
|
setFileOpen(true);
|
||||||
updateWindowTitle();
|
updateWindowTitle();
|
||||||
|
@ -170,6 +182,56 @@ void Controller::save()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Shows a native file dialog if supported; otherwise returns false.
|
||||||
|
* \remarks If supported, this method will load/create the selected file (according to \a existing).
|
||||||
|
*/
|
||||||
|
bool Controller::showNativeFileDialog(bool existing)
|
||||||
|
{
|
||||||
|
#if defined(Q_OS_ANDROID) && defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER)
|
||||||
|
if (!m_useNativeFileDialog) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return showAndroidFileDialog(existing);
|
||||||
|
#else
|
||||||
|
Q_UNUSED(existing)
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::handleFileSelectionAccepted(const QString &filePath, bool existing)
|
||||||
|
{
|
||||||
|
if (existing) {
|
||||||
|
load(filePath);
|
||||||
|
} else {
|
||||||
|
create(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(Q_OS_ANDROID) && defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER)
|
||||||
|
void Controller::handleFileSelectionAcceptedDescriptor(const QString &filePath, int fileDescriptor, bool existing)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
m_file.setPath(filePath.toStdString());
|
||||||
|
m_file.fileStream().openFromFileDescriptor(fileDescriptor, ios_base::in | ios_base::binary);
|
||||||
|
m_file.fileStream().seekg(0);
|
||||||
|
emitIoError("seeked to begin");
|
||||||
|
char buf[4];
|
||||||
|
m_file.fileStream().read(buf, 4);
|
||||||
|
emitIoError("first 4 byte: " + QString::fromLocal8Bit(buf, 4));
|
||||||
|
m_file.opened();
|
||||||
|
} catch (...) {
|
||||||
|
emitIoError(tr("opening from native file descriptor"));
|
||||||
|
}
|
||||||
|
handleFileSelectionAccepted(filePath, existing);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void Controller::handleFileSelectionCanceled()
|
||||||
|
{
|
||||||
|
emit newNotification(tr("Canceled file selection"));
|
||||||
|
}
|
||||||
|
|
||||||
QStringList Controller::pasteEntries(const QModelIndex &destinationParent, int row)
|
QStringList Controller::pasteEntries(const QModelIndex &destinationParent, int row)
|
||||||
{
|
{
|
||||||
if (m_cutEntries.isEmpty() || !m_entryModel.isNode(destinationParent)) {
|
if (m_cutEntries.isEmpty() || !m_entryModel.isNode(destinationParent)) {
|
||||||
|
@ -236,7 +298,7 @@ void Controller::setFileOpen(bool fileOpen)
|
||||||
void Controller::emitIoError(const QString &when)
|
void Controller::emitIoError(const QString &when)
|
||||||
{
|
{
|
||||||
const auto *const msg = catchIoFailure();
|
const auto *const msg = catchIoFailure();
|
||||||
emit fileError(tr("An IO error occured when %1 the file: ").arg(when) + QString::fromLocal8Bit(msg));
|
emit fileError(tr("An IO error occured when %1 the file %2: ").arg(when, m_filePath) + QString::fromLocal8Bit(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace QtGui
|
} // namespace QtGui
|
||||||
|
|
|
@ -29,6 +29,8 @@ class Controller : public QObject {
|
||||||
Q_PROPERTY(QList<QPersistentModelIndex> cutEntries READ cutEntries WRITE setCutEntries NOTIFY cutEntriesChanged)
|
Q_PROPERTY(QList<QPersistentModelIndex> cutEntries READ cutEntries WRITE setCutEntries NOTIFY cutEntriesChanged)
|
||||||
Q_PROPERTY(bool canPaste READ canPaste NOTIFY cutEntriesChanged)
|
Q_PROPERTY(bool canPaste READ canPaste NOTIFY cutEntriesChanged)
|
||||||
Q_PROPERTY(QStringList recentFiles READ recentFiles NOTIFY recentFilesChanged)
|
Q_PROPERTY(QStringList recentFiles READ recentFiles NOTIFY recentFilesChanged)
|
||||||
|
Q_PROPERTY(bool useNativeFileDialog READ useNativeFileDialog WRITE setUseNativeFileDialog NOTIFY useNativeFileDialogChanged)
|
||||||
|
Q_PROPERTY(bool supportsNativeFileDialog READ supportsNativeFileDialog NOTIFY supportsNativeFileDialogChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Controller(QSettings &settings, const QString &filePath = QString(), QObject *parent = nullptr);
|
explicit Controller(QSettings &settings, const QString &filePath = QString(), QObject *parent = nullptr);
|
||||||
|
@ -53,6 +55,9 @@ public:
|
||||||
Q_INVOKABLE bool copyToClipboard(const QString &text) const;
|
Q_INVOKABLE bool copyToClipboard(const QString &text) const;
|
||||||
bool canPaste() const;
|
bool canPaste() const;
|
||||||
const QStringList &recentFiles() const;
|
const QStringList &recentFiles() const;
|
||||||
|
bool useNativeFileDialog() const;
|
||||||
|
void setUseNativeFileDialog(bool useNativeFileDialog);
|
||||||
|
bool supportsNativeFileDialog() const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void init();
|
void init();
|
||||||
|
@ -60,6 +65,12 @@ public slots:
|
||||||
void create(const QString &filePath = QString());
|
void create(const QString &filePath = QString());
|
||||||
void close();
|
void close();
|
||||||
void save();
|
void save();
|
||||||
|
bool showNativeFileDialog(bool existing);
|
||||||
|
void handleFileSelectionAccepted(const QString &filePath, bool existing);
|
||||||
|
#if defined(Q_OS_ANDROID) && defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER)
|
||||||
|
void handleFileSelectionAcceptedDescriptor(const QString &filePath, int fileDescriptor, bool existing);
|
||||||
|
#endif
|
||||||
|
void handleFileSelectionCanceled();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void filePathChanged(const QString &newFilePath);
|
void filePathChanged(const QString &newFilePath);
|
||||||
|
@ -75,6 +86,9 @@ signals:
|
||||||
void currentAccountChanged();
|
void currentAccountChanged();
|
||||||
void cutEntriesChanged(const QList<QPersistentModelIndex> &cutEntries);
|
void cutEntriesChanged(const QList<QPersistentModelIndex> &cutEntries);
|
||||||
void recentFilesChanged(const QStringList &recentFiles);
|
void recentFilesChanged(const QStringList &recentFiles);
|
||||||
|
void newNotification(const QString &message);
|
||||||
|
void useNativeFileDialogChanged(bool useNativeFileDialog);
|
||||||
|
void supportsNativeFileDialogChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void resetFileStatus();
|
void resetFileStatus();
|
||||||
|
@ -95,6 +109,7 @@ private:
|
||||||
QStringList m_recentFiles;
|
QStringList m_recentFiles;
|
||||||
bool m_fileOpen;
|
bool m_fileOpen;
|
||||||
bool m_fileModified;
|
bool m_fileModified;
|
||||||
|
bool m_useNativeFileDialog;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline const QString &Controller::filePath() const
|
inline const QString &Controller::filePath() const
|
||||||
|
@ -178,6 +193,27 @@ inline const QStringList &Controller::recentFiles() const
|
||||||
return m_recentFiles;
|
return m_recentFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool Controller::useNativeFileDialog() const
|
||||||
|
{
|
||||||
|
return m_useNativeFileDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void Controller::setUseNativeFileDialog(bool useNativeFileDialog)
|
||||||
|
{
|
||||||
|
if (m_useNativeFileDialog != useNativeFileDialog) {
|
||||||
|
emit useNativeFileDialogChanged(m_useNativeFileDialog = useNativeFileDialog);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool Controller::supportsNativeFileDialog() const
|
||||||
|
{
|
||||||
|
#if defined(Q_OS_ANDROID) && defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER)
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace QtGui
|
} // namespace QtGui
|
||||||
|
|
||||||
#endif // QT_QUICK_GUI_CONTROLLER_H
|
#endif // QT_QUICK_GUI_CONTROLLER_H
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
#include "./initiatequick.h"
|
#include "./initiatequick.h"
|
||||||
#include "./controller.h"
|
#include "./controller.h"
|
||||||
|
#ifdef Q_OS_ANDROID
|
||||||
|
#include "./android.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "resources/config.h"
|
#include "resources/config.h"
|
||||||
|
|
||||||
|
@ -8,11 +11,11 @@
|
||||||
#include <qtutilities/resources/resources.h>
|
#include <qtutilities/resources/resources.h>
|
||||||
|
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
|
#include <QIcon>
|
||||||
#include <QQmlApplicationEngine>
|
#include <QQmlApplicationEngine>
|
||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QTextCodec>
|
#include <QTextCodec>
|
||||||
#include <QIcon>
|
|
||||||
#include <QtQml>
|
#include <QtQml>
|
||||||
|
|
||||||
#ifdef PASSWORD_MANAGER_GUI_QTWIDGETS
|
#ifdef PASSWORD_MANAGER_GUI_QTWIDGETS
|
||||||
|
@ -36,8 +39,8 @@ enum RelevantFlags {
|
||||||
DrawsSystemBarBackgrounds = 0x80000000,
|
DrawsSystemBarBackgrounds = 0x80000000,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
} // namespace WindowManager
|
||||||
}
|
} // namespace Android
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int runQuickGui(int argc, char *argv[], const QtConfigArguments &qtConfigArgs, const QString &file)
|
int runQuickGui(int argc, char *argv[], const QtConfigArguments &qtConfigArgs, const QString &file)
|
||||||
|
@ -81,6 +84,9 @@ int runQuickGui(int argc, char *argv[], const QtConfigArguments &qtConfigArgs, c
|
||||||
// init Quick GUI
|
// init Quick GUI
|
||||||
QQmlApplicationEngine engine;
|
QQmlApplicationEngine engine;
|
||||||
Controller controller(settings, file);
|
Controller controller(settings, file);
|
||||||
|
#ifdef Q_OS_ANDROID
|
||||||
|
registerControllerForAndroid(&controller);
|
||||||
|
#endif
|
||||||
auto *const context(engine.rootContext());
|
auto *const context(engine.rootContext());
|
||||||
context->setContextProperty(QStringLiteral("userPaths"), userPaths);
|
context->setContextProperty(QStringLiteral("userPaths"), userPaths);
|
||||||
context->setContextProperty(QStringLiteral("nativeInterface"), &controller);
|
context->setContextProperty(QStringLiteral("nativeInterface"), &controller);
|
||||||
|
|
Loading…
Reference in New Issue