Add "Save as" option in Quick GUI

This commit is contained in:
Marius Kittler 2019-06-24 18:51:47 +02:00 committed by Martchus
parent dd77f56a40
commit 0e2af9ff2e
6 changed files with 127 additions and 79 deletions

View File

@ -12,16 +12,16 @@ 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;
private final int REQUEST_CODE_OPEN_EXISTING_FILE = 1;
private final int REQUEST_CODE_CREATE_NEW_FILE = 2;
private final int REQUEST_CODE_SAVE_FILE_AS = 3;
/*!
* \brief Shows the native Android file dialog. Results are handled in onActivityResult().
*/
public boolean showAndroidFileDialog(boolean existing) {
public boolean showAndroidFileDialog(boolean existing, boolean createNew) {
String action = existing ? Intent.ACTION_OPEN_DOCUMENT : Intent.ACTION_CREATE_DOCUMENT;
int requestCode = existing ? REQUEST_CODE_PICK_EXISTING_FILE : REQUEST_CODE_PICK_NEW_FILE;
int requestCode = existing ? REQUEST_CODE_OPEN_EXISTING_FILE : (createNew ? REQUEST_CODE_CREATE_NEW_FILE : REQUEST_CODE_SAVE_FILE_AS);
Intent intent = new Intent(action);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
@ -51,9 +51,12 @@ public class Activity extends QtActivity {
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CODE_PICK_EXISTING_FILE:
case REQUEST_CODE_PICK_NEW_FILE:
boolean existingFile = requestCode == REQUEST_CODE_PICK_EXISTING_FILE;
case REQUEST_CODE_OPEN_EXISTING_FILE:
case REQUEST_CODE_CREATE_NEW_FILE:
case REQUEST_CODE_SAVE_FILE_AS:
boolean createNew = requestCode == REQUEST_CODE_CREATE_NEW_FILE;
boolean existingFile = requestCode == REQUEST_CODE_OPEN_EXISTING_FILE;
boolean saveAs = requestCode == REQUEST_CODE_SAVE_FILE_AS;
if (resultCode != RESULT_OK) {
onAndroidFileDialogRejected();
@ -65,7 +68,7 @@ public class Activity extends QtActivity {
try {
DocumentFile file = DocumentFile.fromSingleUri(this, uri);
ParcelFileDescriptor fd = getContentResolver().openFileDescriptor(file.getUri(), existingFile ? "r" : "wt");
onAndroidFileDialogAcceptedDescriptor(file.getUri().toString(), file.getName(), fd.detachFd(), existingFile);
onAndroidFileDialogAcceptedDescriptor(file.getUri().toString(), file.getName(), fd.detachFd(), existingFile, createNew);
} catch (FileNotFoundException e) {
onAndroidError("Failed to find selected file.");
}
@ -74,7 +77,7 @@ public class Activity extends QtActivity {
String fileName = data.getDataString();
if (fileName != null) {
onAndroidFileDialogAccepted(fileName, existingFile);
onAndroidFileDialogAccepted(fileName, existingFile, createNew);
return;
}
onAndroidError("Failed to read result from Android's file dialog.");
@ -85,7 +88,7 @@ public class Activity extends QtActivity {
}
public static native void onAndroidError(String message);
public static native void onAndroidFileDialogAccepted(String fileName, boolean existing);
public static native void onAndroidFileDialogAcceptedDescriptor(String nativeUrl, String fileName, int fileDescriptor, boolean existing);
public static native void onAndroidFileDialogAccepted(String fileName, boolean existing, boolean createNew);
public static native void onAndroidFileDialogAcceptedDescriptor(String nativeUrl, String fileName, int fileDescriptor, boolean existing, boolean createNew);
public static native void onAndroidFileDialogRejected();
}

View File

@ -128,6 +128,13 @@ Kirigami.ApplicationWindow {
onTriggered: nativeInterface.save()
shortcut: StandardKey.Save
},
Kirigami.Action {
text: qsTr("Save as")
enabled: nativeInterface.fileOpen
iconName: "document-save-as"
onTriggered: fileDialog.saveAs()
shortcut: StandardKey.SaveAs
},
Kirigami.Action {
text: nativeInterface.passwordSet ? qsTr("Change password") : qsTr(
"Add password")
@ -250,19 +257,21 @@ Kirigami.ApplicationWindow {
FileDialog {
id: fileDialog
title: selectExisting ? qsTr("Select an existing file") : qsTr(
"Select path for new file")
property bool createNewFile: false
title: selectExisting ? qsTr("Select an existing file") : (saveAs ? qsTr("Select path to save file") : qsTr("Select path for new file"))
onAccepted: {
if (fileUrls.length < 1) {
return
}
nativeInterface.handleFileSelectionAccepted(fileUrls[0],
this.selectExisting)
nativeInterface.handleFileSelectionAccepted(fileUrls[0], "",
this.selectExisting,
this.createNewFile)
}
onRejected: nativeInterface.handleFileSelectionCanceled()
function show() {
if (nativeInterface.showNativeFileDialog(this.selectExisting)) {
if (nativeInterface.showNativeFileDialog(this.selectExisting,
this.createNewFile)) {
return
}
// fallback to the Qt Quick file dialog if a native implementation is not available
@ -270,10 +279,17 @@ Kirigami.ApplicationWindow {
}
function openExisting() {
this.selectExisting = true
this.createNewFile = false
this.show()
}
function createNew() {
this.selectExisting = false
this.createNewFile = true
this.show()
}
function saveAs() {
this.selectExisting = false
this.createNewFile = false
this.show()
}
}
@ -325,12 +341,16 @@ Kirigami.ApplicationWindow {
}
}
onFileError: {
if (retryAction.length === 0) {
var retryMethod = null
if (retryAction === "load" || retryAction === "save") {
retryMethod = retryAction
}
if (retryMethod) {
showPassiveNotification(errorMessage)
} else {
showPassiveNotification(errorMessage, 2500, qsTr("Retry"),
function () {
nativeInterface[retryAction]()
nativeInterface[retryMethod]()
})
}
}
@ -396,7 +416,11 @@ Kirigami.ApplicationWindow {
Kirigami.Action {
property string filePath
text: filePath.substring(filePath.lastIndexOf('/') + 1)
onTriggered: nativeInterface.load(filePath)
onTriggered: {
nativeInterface.clear()
nativeInterface.filePath = filePath
nativeInterface.load()
}
}
}

View File

@ -50,9 +50,9 @@ void registerControllerForAndroid(Controller *controller)
controllerForAndroid = controller;
}
bool showAndroidFileDialog(bool existing)
bool showAndroidFileDialog(bool existing, bool createNew)
{
return QtAndroid::androidActivity().callMethod<jboolean>("showAndroidFileDialog", "(Z)Z", existing);
return QtAndroid::androidActivity().callMethod<jboolean>("showAndroidFileDialog", "(ZZ)Z", existing, createNew);
}
int openFileDescriptorFromAndroidContentUrl(const QString &url, const QString &mode)
@ -105,17 +105,17 @@ static void onAndroidError(JNIEnv *, jobject, jstring message)
QtGui::controllerForAndroid, "newNotification", Qt::QueuedConnection, Q_ARG(QString, QAndroidJniObject::fromLocalRef(message).toString()));
}
static void onAndroidFileDialogAccepted(JNIEnv *, jobject, jstring fileName, jboolean existing)
static void onAndroidFileDialogAccepted(JNIEnv *, jobject, jstring fileName, jboolean existing, jboolean createNew)
{
QMetaObject::invokeMethod(QtGui::controllerForAndroid, "handleFileSelectionAccepted", Qt::QueuedConnection,
Q_ARG(QString, QAndroidJniObject::fromLocalRef(fileName).toString()), Q_ARG(bool, existing));
Q_ARG(QString, QAndroidJniObject::fromLocalRef(fileName).toString()), Q_ARG(bool, existing), Q_ARG(bool, createNew));
}
static void onAndroidFileDialogAcceptedDescriptor(JNIEnv *, jobject, jstring nativeUrl, jstring fileName, jint fileHandle, jboolean existing)
static void onAndroidFileDialogAcceptedDescriptor(JNIEnv *, jobject, jstring nativeUrl, jstring fileName, jint fileHandle, jboolean existing, jboolean createNew)
{
QMetaObject::invokeMethod(QtGui::controllerForAndroid, "handleFileSelectionAcceptedDescriptor", Qt::QueuedConnection,
Q_ARG(QString, QAndroidJniObject::fromLocalRef(nativeUrl).toString()), Q_ARG(QString, QAndroidJniObject::fromLocalRef(fileName).toString()),
Q_ARG(int, fileHandle), Q_ARG(bool, existing));
Q_ARG(int, fileHandle), Q_ARG(bool, existing), Q_ARG(bool, createNew));
}
static void onAndroidFileDialogRejected(JNIEnv *, jobject)
@ -144,8 +144,8 @@ JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *)
// 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) },
{ "onAndroidFileDialogAcceptedDescriptor", "(Ljava/lang/String;Ljava/lang/String;IZ)V",
{ "onAndroidFileDialogAccepted", "(Ljava/lang/String;ZZ)V", reinterpret_cast<void *>(onAndroidFileDialogAccepted) },
{ "onAndroidFileDialogAcceptedDescriptor", "(Ljava/lang/String;Ljava/lang/String;IZZ)V",
reinterpret_cast<void *>(onAndroidFileDialogAcceptedDescriptor) },
{ "onAndroidFileDialogRejected", "()V", reinterpret_cast<void *>(onAndroidFileDialogRejected) },
};

View File

@ -12,7 +12,7 @@ class Controller;
void applyThemingForAndroid();
void registerControllerForAndroid(Controller *controller);
bool showAndroidFileDialog(bool existing);
bool showAndroidFileDialog(bool existing, bool createNew);
int openFileDescriptorFromAndroidContentUrl(const QString &url, const QString &mode);
void writeToAndroidLog(QtMsgType type, const QMessageLogContext &context, const QString &msg);
void setupAndroidSpecifics();

View File

@ -77,25 +77,21 @@ void Controller::setFilePath(const QString &filePath)
if (filePath.startsWith(QLatin1String("file:"))) {
actualFilePath = filePath.midRef(5);
}
while (filePath.startsWith(QLatin1String("//"))) {
while (actualFilePath.startsWith(QLatin1String("//"))) {
actualFilePath = actualFilePath.mid(1);
}
// skip if this path is already set
if (m_filePath == actualFilePath) {
return;
}
// assign full file path and file name
m_file.clear();
m_file.setPath(filePath.toLocal8Bit().data());
m_fileName = QString::fromLocal8Bit(CppUtilities::fileName(m_file.path()).data());
emit filePathChanged(m_filePath = filePath);
// clear password so we don't use the password from the previous file
m_password.clear();
m_filePath = actualFilePath.toString();
m_file.setPath(m_filePath.toLocal8Bit().toStdString());
const auto fileName = CppUtilities::fileName(m_file.path());
m_fileName = QString::fromLocal8Bit(fileName.data(), static_cast<int>(fileName.size()));
emit filePathChanged(m_filePath);
// handle recent files
if (m_filePath.isEmpty()) {
return;
}
const auto index = m_recentFiles.indexOf(m_filePath);
if (!index) {
return;
@ -127,13 +123,8 @@ void Controller::init()
}
}
void Controller::load(const QString &filePath)
void Controller::load()
{
if (!filePath.isEmpty()) {
setFilePath(filePath);
}
resetFileStatus();
try {
m_file.load();
m_entryModel.setRootEntry(m_file.rootEntry());
@ -158,17 +149,9 @@ void Controller::load(const QString &filePath)
}
}
void Controller::create(const QString &filePath)
void Controller::create()
{
if (!filePath.isEmpty()) {
setFilePath(filePath);
}
resetFileStatus();
try {
if (filePath.isEmpty()) {
m_file.clear();
}
m_file.create();
} catch (...) {
emitFileError(tr("creating"));
@ -190,9 +173,20 @@ void Controller::close()
}
}
void Controller::clear()
{
try {
m_file.close();
} catch (...) {
emitFileError(tr("closing"));
}
m_file.clear();
resetFileStatus();
}
PasswordFileSaveFlags Controller::prepareSaving()
{
auto flags = PasswordFileSaveFlags::Compression | PasswordFileSaveFlags::PasswordHashing;
auto flags = PasswordFileSaveFlags::Compression | PasswordFileSaveFlags::PasswordHashing | PasswordFileSaveFlags::AllowToCreateNewFile;
if (!m_password.isEmpty()) {
flags |= PasswordFileSaveFlags::Encryption;
const auto passwordUtf8(m_password.toUtf8());
@ -216,7 +210,7 @@ void Controller::save()
qDebug() << "Opening new fd for saving, native url: " << m_nativeUrl;
const auto newFileDescriptor = openFileDescriptorFromAndroidContentUrl(m_nativeUrl, QStringLiteral("wt"));
if (newFileDescriptor < 0) {
emit fileError(tr("Unable to open file descriptor for saving the file."));
emit fileError(tr("Unable to open file descriptor for saving the file."), QStringLiteral("save"));
return;
}
@ -243,43 +237,69 @@ 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)
bool Controller::showNativeFileDialog(bool existing, bool createNew)
{
#if defined(Q_OS_ANDROID) && defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER)
if (!m_useNativeFileDialog) {
return false;
}
return showAndroidFileDialog(existing);
return showAndroidFileDialog(existing, createNew);
#else
Q_UNUSED(existing)
Q_UNUSED(createNew)
return false;
#endif
}
void Controller::handleFileSelectionAccepted(const QString &filePath, bool existing)
void Controller::handleFileSelectionAccepted(const QString &filePath, const QString &nativeUrl, bool existing, bool createNew)
{
m_nativeUrl.clear();
m_nativeUrl = nativeUrl;
// assign the "ordinary" file path if one has been passed; otherwise the caller is responsible for handling this
const auto saveAs = !existing && !createNew;
if (!filePath.isEmpty()) {
// clear leftovers from possibly previously opened file unless we want to save the current file under a different location
if (!saveAs) {
m_file.clear();
}
setFilePath(filePath);
}
cout << "path is still " << m_file.path() << " (2)" << endl;
if (!saveAs) {
resetFileStatus();
}
if (existing) {
load(filePath);
} else {
create(filePath);
load();
} else if (createNew) {
create();
} else if (saveAs) {
save();
}
}
#if defined(Q_OS_ANDROID) && defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER)
void Controller::handleFileSelectionAcceptedDescriptor(const QString &nativeUrl, const QString &fileName, int fileDescriptor, bool existing)
void Controller::handleFileSelectionAcceptedDescriptor(
const QString &nativeUrl, const QString &fileName, int fileDescriptor, bool existing, bool createNew)
{
qDebug() << "Opening file descriptor for native url: " << nativeUrl;
qDebug() << "(existing: " << existing << ", create new: " << createNew << ")";
try {
qDebug() << "Opening fd for native url: " << nativeUrl;
// clear leftovers from possibly previously opened file unless we want to save the current file under a different location
if (existing || createNew) {
m_file.clear();
}
m_file.setPath(fileName.toStdString());
m_file.fileStream().open(fileDescriptor, ios_base::in | ios_base::binary);
m_file.opened();
} catch (...) {
emitFileError(tr("opening from native file descriptor"));
emitFileError(existing ? QStringLiteral("load") : (createNew ? QStringLiteral("create") : QStringLiteral("save")));
}
emit filePathChanged(m_filePath = m_fileName = fileName);
handleFileSelectionAccepted(QString(), existing);
m_nativeUrl = nativeUrl;
handleFileSelectionAccepted(QString(), nativeUrl, existing, createNew);
}
#endif

View File

@ -29,11 +29,11 @@ class Controller : public QObject {
Q_PROPERTY(EntryModel *entryModel READ entryModel NOTIFY entryModelChanged)
Q_PROPERTY(EntryFilterModel *entryFilterModel READ entryFilterModel NOTIFY entryFilterModelChanged)
Q_PROPERTY(FieldModel *fieldModel READ fieldModel NOTIFY fieldModelChanged)
Q_PROPERTY(Io::AccountEntry *currentAccount READ currentAccount WRITE setCurrentAccount NOTIFY currentAccountChanged)
//Q_PROPERTY(Io::AccountEntry *currentAccount READ currentAccount WRITE setCurrentAccount NOTIFY currentAccountChanged)
Q_PROPERTY(QModelIndex currentAccountIndex READ currentAccountIndex WRITE setCurrentAccountIndex NOTIFY currentAccountChanged)
Q_PROPERTY(QString currentAccountName READ currentAccountName NOTIFY currentAccountChanged)
Q_PROPERTY(bool hasCurrentAccount READ hasCurrentAccount NOTIFY currentAccountChanged)
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(QStringList recentFiles READ recentFiles RESET clearRecentFiles NOTIFY recentFilesChanged)
Q_PROPERTY(bool useNativeFileDialog READ useNativeFileDialog WRITE setUseNativeFileDialog NOTIFY useNativeFileDialogChanged)
@ -88,14 +88,15 @@ public:
public slots:
void init();
void load(const QString &filePath = QString());
void create(const QString &filePath = QString());
void load();
void create();
void close();
void clear();
void save();
bool showNativeFileDialog(bool existing);
void handleFileSelectionAccepted(const QString &filePath, bool existing);
bool showNativeFileDialog(bool existing, bool createNew);
void handleFileSelectionAccepted(const QString &filePath, const QString &nativeUrl, bool existing, bool createNew);
#if defined(Q_OS_ANDROID) && defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER)
void handleFileSelectionAcceptedDescriptor(const QString &nativeUrl, const QString &fileName, int fileDescriptor, bool existing);
void handleFileSelectionAcceptedDescriptor(const QString &nativeUrl, const QString &fileName, int fileDescriptor, bool existing, bool createNew);
#endif
void handleFileSelectionCanceled();
void undo();
@ -109,7 +110,7 @@ signals:
void passwordRequired(const QString &filePath);
void windowTitleChanged(const QString &windowTitle);
void fileOpenChanged(bool fileOpen);
void fileError(const QString &errorMessage, const QString &retryAction = QString());
void fileError(const QString &errorMessage, const QString &retryAction);
void fileSaved();
void entryModelChanged();
void entryFilterModelChanged();