2018-05-20 01:54:08 +02:00
|
|
|
#include "./controller.h"
|
2018-09-04 00:52:43 +02:00
|
|
|
#include "./android.h"
|
2018-05-20 01:54:08 +02:00
|
|
|
|
|
|
|
#include <passwordfile/io/cryptoexception.h>
|
|
|
|
#include <passwordfile/io/parsingexception.h>
|
|
|
|
|
|
|
|
#include <qtutilities/misc/dialogutils.h>
|
|
|
|
|
2018-09-04 00:52:43 +02:00
|
|
|
#include <c++utilities/io/nativefilestream.h>
|
2018-06-14 23:19:28 +02:00
|
|
|
#include <c++utilities/io/path.h>
|
2018-05-20 01:54:08 +02:00
|
|
|
|
2018-06-16 15:37:55 +02:00
|
|
|
#ifndef QT_NO_CLIPBOARD
|
|
|
|
#include <QClipboard>
|
|
|
|
#endif
|
2019-06-12 21:02:57 +02:00
|
|
|
#if defined(CPP_UTILITIES_DEBUG_BUILD) || (defined(Q_OS_ANDROID) && defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER))
|
2018-09-08 19:59:15 +02:00
|
|
|
#include <QDebug>
|
|
|
|
#endif
|
2018-05-20 01:54:08 +02:00
|
|
|
#include <QDir>
|
|
|
|
#include <QFileInfo>
|
2018-06-16 15:37:55 +02:00
|
|
|
#include <QGuiApplication>
|
2018-05-20 01:54:08 +02:00
|
|
|
#include <QIcon>
|
2018-06-16 15:07:46 +02:00
|
|
|
#include <QSettings>
|
2018-05-20 01:54:08 +02:00
|
|
|
#include <QStringBuilder>
|
|
|
|
|
2018-09-10 22:46:04 +02:00
|
|
|
#include <cassert>
|
2018-05-20 01:54:08 +02:00
|
|
|
#include <stdexcept>
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
using namespace Io;
|
2019-06-10 22:44:59 +02:00
|
|
|
using namespace CppUtilities;
|
|
|
|
using namespace QtUtilities;
|
2018-05-20 01:54:08 +02:00
|
|
|
|
|
|
|
namespace QtGui {
|
|
|
|
|
2018-06-16 15:07:46 +02:00
|
|
|
Controller::Controller(QSettings &settings, const QString &filePath, QObject *parent)
|
2018-05-20 01:54:08 +02:00
|
|
|
: QObject(parent)
|
2018-06-16 15:07:46 +02:00
|
|
|
, m_settings(settings)
|
2018-11-22 22:05:49 +01:00
|
|
|
#ifdef PASSWORD_MANAGER_UNDO_SUPPORT
|
|
|
|
, m_entryModel(&m_undoStack)
|
|
|
|
, m_fieldModel(&m_undoStack)
|
|
|
|
#endif
|
2018-05-20 01:54:08 +02:00
|
|
|
, m_fileOpen(false)
|
|
|
|
, m_fileModified(false)
|
2018-09-04 00:52:43 +02:00
|
|
|
, m_useNativeFileDialog(false)
|
2018-11-20 00:45:05 +01:00
|
|
|
, m_filterAsDialog(
|
|
|
|
#ifdef Q_OS_ANDROID
|
|
|
|
true
|
|
|
|
#else
|
|
|
|
false
|
|
|
|
#endif
|
|
|
|
)
|
2018-05-20 01:54:08 +02:00
|
|
|
{
|
2018-12-03 00:30:16 +01:00
|
|
|
m_fieldModel.setPasswordVisibility(PasswordVisibility::Never);
|
2018-11-20 18:07:11 +01:00
|
|
|
m_entryFilterModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
|
2018-05-20 01:54:08 +02:00
|
|
|
m_entryFilterModel.setSourceModel(&m_entryModel);
|
2018-09-10 22:46:04 +02:00
|
|
|
connect(&m_entryModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &Controller::handleEntriesRemoved);
|
2018-06-16 15:07:46 +02:00
|
|
|
|
2018-11-22 22:05:49 +01:00
|
|
|
#ifdef PASSWORD_MANAGER_UNDO_SUPPORT
|
|
|
|
connect(&m_undoStack, &QUndoStack::undoTextChanged, this, &Controller::undoTextChanged);
|
|
|
|
connect(&m_undoStack, &QUndoStack::redoTextChanged, this, &Controller::redoTextChanged);
|
|
|
|
#endif
|
|
|
|
|
2018-06-16 15:07:46 +02:00
|
|
|
// share settings with main window
|
|
|
|
m_settings.beginGroup(QStringLiteral("mainwindow"));
|
|
|
|
m_recentFiles = m_settings.value(QStringLiteral("recententries")).toStringList();
|
2018-09-04 00:52:43 +02:00
|
|
|
m_useNativeFileDialog = m_settings.value(QStringLiteral("usenativefiledialog"), m_useNativeFileDialog).toBool();
|
2018-09-16 19:16:12 +02:00
|
|
|
connect(this, &Controller::recentFilesChanged, this, &Controller::handleRecentFilesChanged);
|
2018-06-16 15:07:46 +02:00
|
|
|
|
|
|
|
// set initial file path
|
|
|
|
setFilePath(filePath);
|
2018-05-20 01:54:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Controller::setFilePath(const QString &filePath)
|
|
|
|
{
|
2018-06-16 15:07:46 +02:00
|
|
|
// get rid of file:// prefix
|
|
|
|
QStringRef actualFilePath(&filePath);
|
|
|
|
if (filePath.startsWith(QLatin1String("file:"))) {
|
|
|
|
actualFilePath = filePath.midRef(5);
|
|
|
|
}
|
2019-06-24 18:51:47 +02:00
|
|
|
while (actualFilePath.startsWith(QLatin1String("//"))) {
|
2018-06-16 15:07:46 +02:00
|
|
|
actualFilePath = actualFilePath.mid(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// assign full file path and file name
|
2019-06-24 18:51:47 +02:00
|
|
|
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);
|
2018-09-10 19:54:49 +02:00
|
|
|
|
2018-06-16 15:07:46 +02:00
|
|
|
// handle recent files
|
2019-06-24 18:51:47 +02:00
|
|
|
if (m_filePath.isEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
2018-09-04 00:52:43 +02:00
|
|
|
const auto index = m_recentFiles.indexOf(m_filePath);
|
2018-06-16 15:07:46 +02:00
|
|
|
if (!index) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (index < 0) {
|
|
|
|
m_recentFiles.prepend(m_filePath);
|
|
|
|
} else if (index > 0) {
|
|
|
|
m_recentFiles[index].swap(m_recentFiles.first());
|
|
|
|
}
|
|
|
|
while (m_recentFiles.size() > 10) {
|
|
|
|
m_recentFiles.removeLast();
|
|
|
|
}
|
|
|
|
emit recentFilesChanged(m_recentFiles);
|
2018-05-20 01:54:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Controller::setPassword(const QString &password)
|
|
|
|
{
|
|
|
|
if (m_password == password) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_file.setPassword(password.toUtf8().data());
|
|
|
|
emit passwordChanged(m_password = password);
|
|
|
|
}
|
|
|
|
|
2018-06-16 15:15:25 +02:00
|
|
|
void Controller::init()
|
|
|
|
{
|
|
|
|
if (!m_filePath.isEmpty()) {
|
|
|
|
load();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-24 18:51:47 +02:00
|
|
|
void Controller::load()
|
2018-05-20 01:54:08 +02:00
|
|
|
{
|
|
|
|
try {
|
|
|
|
m_file.load();
|
|
|
|
m_entryModel.setRootEntry(m_file.rootEntry());
|
2018-12-08 19:18:35 +01:00
|
|
|
if (!m_entryModel.rootEntry()) {
|
|
|
|
emit fileError(tr("An error occured when opening the file: root element missing"), QStringLiteral("load"));
|
|
|
|
return;
|
|
|
|
}
|
2018-05-20 01:54:08 +02:00
|
|
|
setFileOpen(true);
|
|
|
|
updateWindowTitle();
|
|
|
|
} catch (const CryptoException &e) {
|
2018-12-22 02:24:48 +01:00
|
|
|
if ((m_file.saveOptions() & PasswordFileSaveFlags::Encryption) && m_password.isEmpty()) {
|
2018-05-20 01:54:08 +02:00
|
|
|
emit passwordRequired(m_filePath);
|
|
|
|
} else {
|
2018-09-10 19:54:49 +02:00
|
|
|
// clear password since the password which has been provided likely wasn't correct
|
|
|
|
clearPassword();
|
2018-11-20 18:06:52 +01:00
|
|
|
emit fileError(tr("A crypto error occured when opening the file: ") + QString::fromLocal8Bit(e.what()), QStringLiteral("load"));
|
2018-05-20 01:54:08 +02:00
|
|
|
}
|
|
|
|
} catch (...) {
|
2020-03-29 00:55:57 +01:00
|
|
|
if ((m_file.saveOptions() & PasswordFileSaveFlags::Encryption) && m_password.isEmpty()) {
|
|
|
|
emit passwordRequired(m_filePath);
|
|
|
|
} else {
|
|
|
|
// clear password since the password which has been provided might not have been correct (although there was no CryptoException)
|
|
|
|
clearPassword();
|
|
|
|
emitFileError(tr("loading"));
|
|
|
|
}
|
2018-05-20 01:54:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-24 18:51:47 +02:00
|
|
|
void Controller::create()
|
2018-05-20 01:54:08 +02:00
|
|
|
{
|
|
|
|
try {
|
|
|
|
m_file.create();
|
|
|
|
} catch (...) {
|
2019-03-13 19:09:31 +01:00
|
|
|
emitFileError(tr("creating"));
|
2018-05-20 01:54:08 +02:00
|
|
|
}
|
2018-09-04 00:52:43 +02:00
|
|
|
|
|
|
|
m_file.generateRootEntry();
|
|
|
|
m_entryModel.setRootEntry(m_file.rootEntry());
|
2020-03-28 23:27:47 +01:00
|
|
|
m_password.clear(); // avoid using the password of previously opened file
|
2018-09-04 00:52:43 +02:00
|
|
|
setFileOpen(true);
|
|
|
|
updateWindowTitle();
|
2018-05-20 01:54:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Controller::close()
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
m_file.close();
|
|
|
|
resetFileStatus();
|
|
|
|
} catch (...) {
|
2019-03-13 19:09:31 +01:00
|
|
|
emitFileError(tr("closing"));
|
2018-05-20 01:54:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-24 18:51:47 +02:00
|
|
|
void Controller::clear()
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
m_file.close();
|
|
|
|
} catch (...) {
|
|
|
|
emitFileError(tr("closing"));
|
|
|
|
}
|
|
|
|
m_file.clear();
|
|
|
|
resetFileStatus();
|
|
|
|
}
|
|
|
|
|
2018-12-21 01:14:41 +01:00
|
|
|
PasswordFileSaveFlags Controller::prepareSaving()
|
2018-05-20 01:54:08 +02:00
|
|
|
{
|
2019-06-24 18:51:47 +02:00
|
|
|
auto flags = PasswordFileSaveFlags::Compression | PasswordFileSaveFlags::PasswordHashing | PasswordFileSaveFlags::AllowToCreateNewFile;
|
2018-12-18 23:17:55 +01:00
|
|
|
if (!m_password.isEmpty()) {
|
|
|
|
flags |= PasswordFileSaveFlags::Encryption;
|
|
|
|
const auto passwordUtf8(m_password.toUtf8());
|
|
|
|
m_file.setPassword(passwordUtf8.data(), static_cast<size_t>(passwordUtf8.size()));
|
|
|
|
} else {
|
|
|
|
m_file.clearPassword();
|
|
|
|
}
|
2018-12-21 01:14:41 +01:00
|
|
|
return flags;
|
|
|
|
}
|
2018-09-04 00:52:43 +02:00
|
|
|
|
2018-12-21 01:14:41 +01:00
|
|
|
void Controller::save()
|
|
|
|
{
|
|
|
|
const auto flags = prepareSaving();
|
2018-12-18 23:17:55 +01:00
|
|
|
try {
|
2018-09-04 00:52:43 +02:00
|
|
|
#if defined(Q_OS_ANDROID) && defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER)
|
|
|
|
if (!m_nativeUrl.isEmpty()) {
|
|
|
|
// ensure file is closed
|
|
|
|
m_file.close();
|
|
|
|
|
|
|
|
// open new file descriptor to replace existing file and allow writing
|
2019-01-12 02:51:13 +01:00
|
|
|
qDebug() << "Opening new fd for saving, native url: " << m_nativeUrl;
|
2018-09-04 00:52:43 +02:00
|
|
|
const auto newFileDescriptor = openFileDescriptorFromAndroidContentUrl(m_nativeUrl, QStringLiteral("wt"));
|
|
|
|
if (newFileDescriptor < 0) {
|
2019-06-24 18:51:47 +02:00
|
|
|
emit fileError(tr("Unable to open file descriptor for saving the file."), QStringLiteral("save"));
|
2018-09-04 00:52:43 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-03-13 19:09:31 +01:00
|
|
|
m_file.fileStream().open(newFileDescriptor, ios_base::out | ios_base::trunc | ios_base::binary);
|
2018-12-18 23:17:55 +01:00
|
|
|
m_file.write(flags);
|
2018-09-04 00:52:43 +02:00
|
|
|
} else {
|
|
|
|
#endif
|
|
|
|
// let libpasswordfile handle everything
|
2018-12-18 23:17:55 +01:00
|
|
|
m_file.save(flags);
|
2018-09-04 00:52:43 +02:00
|
|
|
#if defined(Q_OS_ANDROID) && defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER)
|
|
|
|
}
|
|
|
|
#endif
|
2018-06-12 22:02:37 +02:00
|
|
|
emit fileSaved();
|
2018-05-20 01:54:08 +02:00
|
|
|
} catch (const CryptoException &e) {
|
2018-11-20 18:06:52 +01:00
|
|
|
emit fileError(tr("A crypto error occured when saving the file: ") + QString::fromLocal8Bit(e.what()), QStringLiteral("save"));
|
2018-05-20 01:54:08 +02:00
|
|
|
} catch (...) {
|
2019-03-13 19:09:31 +01:00
|
|
|
emitFileError(tr("saving"));
|
2018-05-20 01:54:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-04 00:52:43 +02:00
|
|
|
/*!
|
|
|
|
* \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).
|
|
|
|
*/
|
2019-06-24 18:51:47 +02:00
|
|
|
bool Controller::showNativeFileDialog(bool existing, bool createNew)
|
2018-09-04 00:52:43 +02:00
|
|
|
{
|
|
|
|
#if defined(Q_OS_ANDROID) && defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER)
|
|
|
|
if (!m_useNativeFileDialog) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-06-24 18:51:47 +02:00
|
|
|
return showAndroidFileDialog(existing, createNew);
|
2018-09-04 00:52:43 +02:00
|
|
|
#else
|
|
|
|
Q_UNUSED(existing)
|
2019-06-24 18:51:47 +02:00
|
|
|
Q_UNUSED(createNew)
|
2018-09-04 00:52:43 +02:00
|
|
|
return false;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2019-06-24 18:51:47 +02:00
|
|
|
void Controller::handleFileSelectionAccepted(const QString &filePath, const QString &nativeUrl, bool existing, bool createNew)
|
2018-09-04 00:52:43 +02:00
|
|
|
{
|
2019-06-24 18:51:47 +02:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2018-09-04 00:52:43 +02:00
|
|
|
if (existing) {
|
2019-06-24 18:51:47 +02:00
|
|
|
load();
|
|
|
|
} else if (createNew) {
|
|
|
|
create();
|
|
|
|
} else if (saveAs) {
|
|
|
|
save();
|
2018-09-04 00:52:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#if defined(Q_OS_ANDROID) && defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER)
|
2019-06-24 18:51:47 +02:00
|
|
|
void Controller::handleFileSelectionAcceptedDescriptor(
|
|
|
|
const QString &nativeUrl, const QString &fileName, int fileDescriptor, bool existing, bool createNew)
|
2018-09-04 00:52:43 +02:00
|
|
|
{
|
2019-06-24 18:51:47 +02:00
|
|
|
qDebug() << "Opening file descriptor for native url: " << nativeUrl;
|
|
|
|
qDebug() << "(existing: " << existing << ", create new: " << createNew << ")";
|
|
|
|
|
2018-09-04 00:52:43 +02:00
|
|
|
try {
|
2019-06-24 18:51:47 +02:00
|
|
|
// 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();
|
|
|
|
}
|
2018-09-04 00:52:43 +02:00
|
|
|
m_file.setPath(fileName.toStdString());
|
2019-03-13 19:09:31 +01:00
|
|
|
m_file.fileStream().open(fileDescriptor, ios_base::in | ios_base::binary);
|
2018-09-04 00:52:43 +02:00
|
|
|
m_file.opened();
|
|
|
|
} catch (...) {
|
2019-06-24 18:51:47 +02:00
|
|
|
emitFileError(existing ? QStringLiteral("load") : (createNew ? QStringLiteral("create") : QStringLiteral("save")));
|
2018-09-04 00:52:43 +02:00
|
|
|
}
|
2019-06-24 18:51:47 +02:00
|
|
|
|
2018-09-04 00:52:43 +02:00
|
|
|
emit filePathChanged(m_filePath = m_fileName = fileName);
|
2019-06-24 18:51:47 +02:00
|
|
|
handleFileSelectionAccepted(QString(), nativeUrl, existing, createNew);
|
2018-09-04 00:52:43 +02:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
void Controller::handleFileSelectionCanceled()
|
|
|
|
{
|
|
|
|
emit newNotification(tr("Canceled file selection"));
|
|
|
|
}
|
|
|
|
|
2018-09-10 22:46:04 +02:00
|
|
|
void Controller::handleEntriesRemoved(const QModelIndex &parentIndex, int first, int last)
|
|
|
|
{
|
|
|
|
// handle deletion of root (currently the view doesn't allow this)
|
|
|
|
const auto *const parentEntry = m_entryModel.entry(parentIndex);
|
|
|
|
if (!parentEntry) {
|
|
|
|
emit entryAboutToBeRemoved(m_entryModel.index(0, 0, QModelIndex()));
|
|
|
|
setCurrentAccount(nullptr);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// assert arguments
|
|
|
|
assert(parentEntry->type() == EntryType::Node);
|
|
|
|
const auto &childEntries = static_cast<const NodeEntry *>(parentEntry)->children();
|
|
|
|
assert(first >= 0 && static_cast<size_t>(first) < childEntries.size());
|
|
|
|
assert(last >= 0 && static_cast<size_t>(last) < childEntries.size());
|
|
|
|
|
|
|
|
// iterate from first to last of the deleted entries
|
|
|
|
const auto *const currentAccount = this->currentAccount();
|
|
|
|
for (; first <= last; ++first) {
|
|
|
|
// inform view about deletion
|
|
|
|
emit entryAboutToBeRemoved(m_entryModel.index(first, 0, parentIndex));
|
|
|
|
|
|
|
|
// unset current account if it is under the deleted node
|
|
|
|
if (!currentAccount) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const auto *const childEntry = childEntries[static_cast<size_t>(first)];
|
|
|
|
switch (childEntry->type()) {
|
|
|
|
case EntryType::Account:
|
|
|
|
if (currentAccount == static_cast<const AccountEntry *>(childEntry)) {
|
|
|
|
setCurrentAccount(nullptr);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case EntryType::Node:
|
2018-12-23 19:02:40 +01:00
|
|
|
if (currentAccount->isIndirectChildOf(static_cast<const NodeEntry *>(childEntry))) {
|
2018-09-10 22:46:04 +02:00
|
|
|
setCurrentAccount(nullptr);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-16 19:16:12 +02:00
|
|
|
void Controller::handleRecentFilesChanged()
|
|
|
|
{
|
|
|
|
m_settings.setValue(QStringLiteral("recententries"), m_recentFiles);
|
|
|
|
}
|
|
|
|
|
2018-09-16 21:37:30 +02:00
|
|
|
QStringList Controller::pasteEntries(const QModelIndex &destinationParentMaybeFromFilterModel, int row)
|
2018-06-11 23:21:15 +02:00
|
|
|
{
|
2018-09-16 21:37:30 +02:00
|
|
|
// skip if no entries have been cut
|
|
|
|
if (m_cutEntries.isEmpty()) {
|
2018-06-12 22:20:43 +02:00
|
|
|
return QStringList();
|
2018-06-11 23:21:15 +02:00
|
|
|
}
|
2018-09-16 21:37:30 +02:00
|
|
|
|
|
|
|
// determine destinationParent and row in the source model
|
|
|
|
QModelIndex destinationParent;
|
|
|
|
if (destinationParentMaybeFromFilterModel.model() == &m_entryFilterModel) {
|
|
|
|
if (row < 0) {
|
|
|
|
row = m_entryFilterModel.rowCount(destinationParentMaybeFromFilterModel);
|
|
|
|
}
|
|
|
|
const auto destinationIndexInFilter = m_entryFilterModel.index(row, 0, destinationParentMaybeFromFilterModel);
|
|
|
|
if (destinationIndexInFilter.isValid()) {
|
|
|
|
const auto destinationIndex = m_entryFilterModel.mapToSource(destinationIndexInFilter);
|
|
|
|
destinationParent = destinationIndex.parent();
|
|
|
|
row = destinationIndex.row();
|
|
|
|
} else {
|
|
|
|
destinationParent = m_entryFilterModel.mapToSource(destinationParentMaybeFromFilterModel);
|
|
|
|
row = -1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
destinationParent = destinationParentMaybeFromFilterModel;
|
|
|
|
}
|
2018-06-11 23:21:15 +02:00
|
|
|
if (row < 0) {
|
|
|
|
row = m_entryModel.rowCount(destinationParent);
|
|
|
|
}
|
2018-06-12 22:20:43 +02:00
|
|
|
|
2018-09-16 21:37:30 +02:00
|
|
|
// skip if destination is no node
|
|
|
|
if (!m_entryModel.isNode(destinationParent)) {
|
|
|
|
return QStringList();
|
|
|
|
}
|
|
|
|
|
|
|
|
// move the entries
|
2018-06-12 22:20:43 +02:00
|
|
|
QStringList successfullyMovedEntries;
|
|
|
|
successfullyMovedEntries.reserve(m_cutEntries.size());
|
2018-06-11 23:21:15 +02:00
|
|
|
for (const QPersistentModelIndex &cutIndex : m_cutEntries) {
|
|
|
|
if (m_entryModel.moveRows(cutIndex.parent(), cutIndex.row(), 1, destinationParent, row)) {
|
2018-06-12 22:20:43 +02:00
|
|
|
successfullyMovedEntries << m_entryModel.data(m_entryModel.index(row, 0, destinationParent)).toString();
|
2018-06-11 23:21:15 +02:00
|
|
|
++row;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// clear the cut entries
|
|
|
|
m_cutEntries.clear();
|
|
|
|
emit cutEntriesChanged(m_cutEntries);
|
2018-06-12 22:20:43 +02:00
|
|
|
return successfullyMovedEntries;
|
2018-06-11 23:21:15 +02:00
|
|
|
}
|
|
|
|
|
2018-06-16 15:37:55 +02:00
|
|
|
bool Controller::copyToClipboard(const QString &text) const
|
|
|
|
{
|
|
|
|
#ifndef QT_NO_CLIPBOARD
|
2018-09-04 00:52:43 +02:00
|
|
|
auto *const clipboard(QGuiApplication::clipboard());
|
2018-06-16 15:37:55 +02:00
|
|
|
if (!clipboard) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
clipboard->setText(text);
|
|
|
|
return true;
|
|
|
|
#else
|
|
|
|
return false;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2018-05-20 01:54:08 +02:00
|
|
|
void Controller::resetFileStatus()
|
|
|
|
{
|
|
|
|
m_entryModel.reset();
|
|
|
|
m_fieldModel.reset();
|
2018-09-10 19:54:49 +02:00
|
|
|
setFileOpen(false);
|
2018-05-20 01:54:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Controller::updateWindowTitle()
|
|
|
|
{
|
|
|
|
if (m_fileOpen) {
|
|
|
|
const QFileInfo file(m_filePath);
|
|
|
|
emit windowTitleChanged(m_windowTitle = file.fileName() % QStringLiteral(" - ") % file.dir().path());
|
|
|
|
} else {
|
|
|
|
emit windowTitleChanged(tr("No file opened."));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Controller::setFileOpen(bool fileOpen)
|
|
|
|
{
|
|
|
|
if (fileOpen != m_fileOpen) {
|
|
|
|
emit fileOpenChanged(m_fileOpen = fileOpen);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-13 19:09:31 +01:00
|
|
|
void Controller::emitFileError(const QString &when)
|
2018-05-20 01:54:08 +02:00
|
|
|
{
|
2018-09-04 00:52:43 +02:00
|
|
|
try {
|
2019-03-13 19:09:31 +01:00
|
|
|
throw;
|
|
|
|
} catch (const std::ios_base::failure &failure) {
|
|
|
|
emit fileError(
|
|
|
|
tr("An IO error occured when %1 the file %2: ").arg(when, m_filePath) + QString::fromLocal8Bit(failure.what()), QStringLiteral("load"));
|
2020-03-28 23:13:34 +01:00
|
|
|
} catch (const runtime_error &e) {
|
|
|
|
emit fileError(tr("An error occured when %1 the file: ").arg(when) + QString::fromLocal8Bit(e.what()), QStringLiteral("save"));
|
2018-09-04 00:52:43 +02:00
|
|
|
} catch (const exception &e) {
|
2018-11-20 18:06:52 +01:00
|
|
|
emit fileError(tr("An unknown exception occured when %1 the file %2: ").arg(when, m_filePath) + QString::fromLocal8Bit(e.what()),
|
|
|
|
QStringLiteral("load"));
|
2018-09-04 00:52:43 +02:00
|
|
|
} catch (...) {
|
2018-11-20 18:06:52 +01:00
|
|
|
emit fileError(tr("An unknown error occured when %1 the file %2.").arg(when, m_filePath), QStringLiteral("load"));
|
2018-09-04 00:52:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-16 19:16:12 +02:00
|
|
|
void Controller::clearRecentFiles()
|
|
|
|
{
|
|
|
|
if (m_recentFiles.isEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_recentFiles.clear();
|
|
|
|
emit recentFilesChanged(m_recentFiles);
|
|
|
|
}
|
|
|
|
|
2018-09-04 00:52:43 +02:00
|
|
|
void Controller::setUseNativeFileDialog(bool useNativeFileDialog)
|
|
|
|
{
|
|
|
|
if (m_useNativeFileDialog == useNativeFileDialog) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
emit useNativeFileDialogChanged(m_useNativeFileDialog = useNativeFileDialog);
|
|
|
|
m_settings.setValue(QStringLiteral("usenativefiledialog"), m_useNativeFileDialog);
|
2018-05-20 01:54:08 +02:00
|
|
|
}
|
|
|
|
|
2018-09-16 21:37:30 +02:00
|
|
|
void Controller::setEntryFilter(const QString &filter)
|
|
|
|
{
|
|
|
|
const auto previousFilter(m_entryFilterModel.filterRegExp().pattern());
|
|
|
|
if (filter == previousFilter) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_entryFilterModel.setFilterRegExp(filter);
|
|
|
|
emit entryFilterChanged(filter);
|
|
|
|
if (previousFilter.isEmpty() != filter.isEmpty()) {
|
|
|
|
emit hasEntryFilterChanged(!filter.isEmpty());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-20 01:54:08 +02:00
|
|
|
} // namespace QtGui
|