From c490c21cd53a9286c86143ef524ad3b22486d295 Mon Sep 17 00:00:00 2001 From: frostasm Date: Tue, 19 Dec 2017 15:36:09 +0200 Subject: [PATCH] Move attachments view to the separate widget EntryAttachmentsWidget --- src/CMakeLists.txt | 1 + src/gui/DatabaseWidget.cpp | 4 +- src/gui/DetailsWidget.cpp | 2 +- src/gui/DetailsWidget.h | 2 +- src/gui/entry/EditEntryWidget.cpp | 291 +------------------ src/gui/entry/EditEntryWidget.h | 16 - src/gui/entry/EditEntryWidgetAdvanced.ui | 81 +----- src/gui/entry/EntryAttachmentsWidget.cpp | 353 +++++++++++++++++++++++ src/gui/entry/EntryAttachmentsWidget.h | 59 ++++ src/gui/entry/EntryAttachmentsWidget.ui | 99 +++++++ 10 files changed, 541 insertions(+), 367 deletions(-) create mode 100644 src/gui/entry/EntryAttachmentsWidget.cpp create mode 100644 src/gui/entry/EntryAttachmentsWidget.h create mode 100644 src/gui/entry/EntryAttachmentsWidget.ui diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index afa30056..216db839 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -130,6 +130,7 @@ set(keepassx_SOURCES gui/entry/EditEntryWidget.cpp gui/entry/EditEntryWidget_p.h gui/entry/EntryAttachmentsModel.cpp + gui/entry/EntryAttachmentsWidget.cpp gui/entry/EntryAttributesModel.cpp gui/entry/EntryHistoryModel.cpp gui/entry/EntryModel.cpp diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index b6e772f6..1ef91a4f 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -111,8 +111,8 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) "border-radius: 5px;"); m_detailsView = new DetailsWidget(this); - connect(m_detailsView, &DetailsWidget::errorOccured, this, [this](const QString& error) { - showMessage(error, MessageWidget::MessageType::Error, false); + connect(m_detailsView, &DetailsWidget::errorOccurred, this, [this](const QString& error) { + showMessage(error, MessageWidget::MessageType::Error); }); QVBoxLayout* vLayout = new QVBoxLayout(rightHandSideWidget); diff --git a/src/gui/DetailsWidget.cpp b/src/gui/DetailsWidget.cpp index eadc5d47..8c4de5fc 100644 --- a/src/gui/DetailsWidget.cpp +++ b/src/gui/DetailsWidget.cpp @@ -376,7 +376,7 @@ void DetailsWidget::openAttachment(const QModelIndex& index) if (!saveOk) { delete tmpFile; - emit errorOccured(tr("Unable to open the attachment:\n").append(tmpFile->errorString())); + emit errorOccurred(tr("Unable to open the attachment:\n").append(tmpFile->errorString())); return; } diff --git a/src/gui/DetailsWidget.h b/src/gui/DetailsWidget.h index c50e2d7e..61097651 100644 --- a/src/gui/DetailsWidget.h +++ b/src/gui/DetailsWidget.h @@ -52,7 +52,7 @@ public: }; signals: - void errorOccured(const QString& error); + void errorOccurred(const QString& error); private slots: void getSelectedEntry(Entry* selectedEntry); diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index c6d49c82..b922f417 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -70,8 +70,6 @@ EditEntryWidget::EditEntryWidget(QWidget* parent) , m_sshAgentWidget(new QWidget()) , m_editWidgetProperties(new EditWidgetProperties()) , m_historyWidget(new QWidget()) - , m_entryAttachments(new EntryAttachments(this)) - , m_attachmentsModel(new EntryAttachmentsModel(m_advancedWidget)) , m_entryAttributes(new EntryAttributes(this)) , m_attributesModel(new EntryAttributesModel(m_advancedWidget)) , m_historyModel(new EntryHistoryModel(this)) @@ -140,24 +138,12 @@ void EditEntryWidget::setupAdvanced() m_advancedUi->setupUi(m_advancedWidget); addPage(tr("Advanced"), FilePath::instance()->icon("categories", "preferences-other"), m_advancedWidget); - m_advancedUi->attachmentsView->setAcceptDrops(false); - m_advancedUi->attachmentsView->viewport()->setAcceptDrops(true); - m_advancedUi->attachmentsView->viewport()->installEventFilter(this); + m_advancedUi->attachmentsWidget->setReadOnly(false); + m_advancedUi->attachmentsWidget->setButtonsVisible(true); - m_attachmentsModel->setEntryAttachments(m_entryAttachments); - m_advancedUi->attachmentsView->setModel(m_attachmentsModel); - m_advancedUi->attachmentsView->horizontalHeader()->setStretchLastSection(true); - m_advancedUi->attachmentsView->horizontalHeader()->resizeSection(0, 600); - m_advancedUi->attachmentsView->setSelectionBehavior(QAbstractItemView::SelectRows); - m_advancedUi->attachmentsView->setSelectionMode(QAbstractItemView::ExtendedSelection); - - connect(m_advancedUi->attachmentsView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), - SLOT(updateAttachmentButtonsEnabled())); - connect(m_advancedUi->attachmentsView, SIGNAL(doubleClicked(QModelIndex)), SLOT(openAttachment(QModelIndex))); - connect(m_advancedUi->saveAttachmentButton, SIGNAL(clicked()), SLOT(saveSelectedAttachments())); - connect(m_advancedUi->openAttachmentButton, SIGNAL(clicked()), SLOT(openSelectedAttachments())); - connect(m_advancedUi->addAttachmentButton, SIGNAL(clicked()), SLOT(insertAttachments())); - connect(m_advancedUi->removeAttachmentButton, SIGNAL(clicked()), SLOT(removeSelectedAttachments())); + connect(m_advancedUi->attachmentsWidget, &EntryAttachmentsWidget::errorOccurred, this, [this](const QString &error) { + showMessage(error, MessageWidget::Error); + }); m_attributesModel->setEntryAttributes(m_entryAttributes); m_advancedUi->attributesView->setModel(m_attributesModel); @@ -581,8 +567,8 @@ void EditEntryWidget::setForms(const Entry* entry, bool restore) m_mainUi->togglePasswordGeneratorButton->setChecked(false); m_mainUi->togglePasswordGeneratorButton->setDisabled(m_history); m_mainUi->passwordGenerator->reset(); - m_advancedUi->addAttachmentButton->setEnabled(!m_history); - updateAttachmentButtonsEnabled(); + + m_advancedUi->attachmentsWidget->setReadOnly(m_history); m_advancedUi->addAttributeButton->setEnabled(!m_history); m_advancedUi->editAttributeButton->setEnabled(false); m_advancedUi->removeAttributeButton->setEnabled(false); @@ -613,7 +599,7 @@ void EditEntryWidget::setForms(const Entry* entry, bool restore) m_mainUi->notesEdit->setPlainText(entry->notes()); - m_entryAttachments->copyDataFrom(entry->attachments()); + m_advancedUi->attachmentsWidget->setEntryAttachments(entry->attachments()); m_entryAttributes->copyCustomKeysFrom(entry->attributes()); if (m_attributesModel->rowCount() != 0) { @@ -749,8 +735,8 @@ void EditEntryWidget::acceptEntry() void EditEntryWidget::updateEntryData(Entry* entry) const { entry->attributes()->copyCustomKeysFrom(m_entryAttributes); - entry->attachments()->copyDataFrom(m_entryAttachments); - + entry->attachments()->copyDataFrom(m_advancedUi->attachmentsWidget->entryAttachments()); + entry->setTitle(m_mainUi->titleEdit->text()); entry->setUsername(m_mainUi->usernameEdit->text()); entry->setUrl(m_mainUi->urlEdit->text()); @@ -807,7 +793,7 @@ void EditEntryWidget::clear() m_entry = nullptr; m_database = nullptr; m_entryAttributes->clear(); - m_entryAttachments->clear(); + m_advancedUi->attachmentsWidget->clearAttachments(); m_autoTypeAssoc->clear(); m_historyModel->clear(); m_iconsWidget->reset(); @@ -950,69 +936,6 @@ void EditEntryWidget::displayAttribute(QModelIndex index, bool showProtected) m_advancedUi->protectAttributeButton->blockSignals(false); } -bool EditEntryWidget::openAttachment(const QModelIndex &index, QString *errorMessage) -{ - const QString filename = m_attachmentsModel->keyByIndex(index); - const QByteArray attachmentData = m_entryAttachments->value(filename); - - // tmp file will be removed once the database (or the application) has been closed - const QString tmpFileTemplate = QDir::temp().absoluteFilePath(QString("XXXXXX.").append(filename)); - QTemporaryFile* tmpFile = new QTemporaryFile(tmpFileTemplate, this); - - const bool saveOk = tmpFile->open() - && tmpFile->write(attachmentData) == attachmentData.size() - && tmpFile->flush(); - if (!saveOk) { - if (errorMessage) { - *errorMessage = tr("Unable to save the attachment:\n").append(tmpFile->errorString()); - } - delete tmpFile; - return false; - } - - tmpFile->close(); - QDesktopServices::openUrl(QUrl::fromLocalFile(tmpFile->fileName())); - - return true; -} - -bool EditEntryWidget::eventFilter(QObject* watched, QEvent* e) -{ - switch (e->type()) { - case QEvent::DragEnter: - case QEvent::DragMove: { - QDropEvent* dropEv = static_cast(e); - const QMimeData* mimeData = dropEv->mimeData(); - if (mimeData->hasUrls()) { - dropEv->acceptProposedAction(); - return true; - } - break; - } - case QEvent::Drop: { - QDropEvent* dropEv = static_cast(e); - const QMimeData* mimeData = dropEv->mimeData(); - if (mimeData->hasUrls()) { - dropEv->acceptProposedAction(); - QStringList filenames; - for (const QUrl url: mimeData->urls()) { - QFileInfo fInfo(url.toLocalFile()); - if (fInfo.isFile()) { - filenames.append(fInfo.absoluteFilePath()); - } - } - insertAttachments(filenames); - - return true; - } - } - default: - break; - } - - return EditWidget::eventFilter(watched, e); -} - void EditEntryWidget::protectCurrentAttribute(bool state) { QModelIndex index = m_advancedUi->attributesView->currentIndex(); @@ -1043,198 +966,6 @@ void EditEntryWidget::revealCurrentAttribute() } } -void EditEntryWidget::insertAttachments(const QStringList& filenames, QString* errorMessage) -{ - QStringList errors; - for (const QString &filename: filenames) { - const QFileInfo fInfo(filename); - QFile file(filename); - QByteArray data; - const bool readOk = file.open(QIODevice::ReadOnly) && Tools::readAllFromDevice(&file, data); - if (readOk) { - m_entryAttachments->set(fInfo.fileName(), data); - } else { - errors.append(QString("%1 - %2").arg(fInfo.fileName(), file.errorString())); - } - } - - if (errorMessage && !errors.isEmpty()) { - *errorMessage = tr("Unable to open files:\n%1").arg(errors.join('\n'));; - } -} - -void EditEntryWidget::insertAttachments() -{ - Q_ASSERT(!m_history); - - QString defaultDir = config()->get("LastAttachmentDir").toString(); - if (defaultDir.isEmpty() || !QDir(defaultDir).exists()) { - defaultDir = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation).value(0); - } - - const QStringList filenames = fileDialog()->getOpenFileNames(this, tr("Select files"), defaultDir); - if (filenames.isEmpty()) { - return; - } - - config()->set("LastAttachmentDir", QFileInfo(filenames.first()).absolutePath()); - - QString error; - insertAttachments(filenames, &error); - if (!error.isEmpty()) { - showMessage(error, MessageWidget::Error); - } -} - -void EditEntryWidget::saveSelectedAttachment() -{ - const QModelIndex index = m_advancedUi->attachmentsView->currentIndex(); - if (!index.isValid()) { - return; - } - - const QString filename = m_attachmentsModel->keyByIndex(index); - QString defaultDirName = config()->get("LastAttachmentDir").toString(); - if (defaultDirName.isEmpty() || !QDir(defaultDirName).exists()) { - defaultDirName = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); - } - - const QString savePath = fileDialog()->getSaveFileName(this, tr("Save attachment"), - QDir(defaultDirName).filePath(filename)); - if (!savePath.isEmpty()) { - config()->set("LastAttachmentDir", QFileInfo(savePath).absolutePath()); - - QFile file(savePath); - const QByteArray attachmentData = m_entryAttachments->value(filename); - const bool saveOk = file.open(QIODevice::WriteOnly) && file.write(attachmentData) == attachmentData.size(); - if (!saveOk) { - showMessage(tr("Unable to save the attachment:\n").append(file.errorString()), MessageWidget::Error); - } - } -} - -void EditEntryWidget::saveSelectedAttachments() -{ - const QModelIndexList indexes = m_advancedUi->attachmentsView->selectionModel()->selectedRows(0); - if (indexes.isEmpty()) { - return; - } else if (indexes.count() == 1) { - saveSelectedAttachment(); - return; - } - - QString defaultDirName = config()->get("LastAttachmentDir").toString(); - if (defaultDirName.isEmpty() || !QDir(defaultDirName).exists()) { - defaultDirName = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); - } - - const QString savePath = fileDialog()->getExistingDirectory(this, tr("Save attachments"), defaultDirName); - if (savePath.isEmpty()) { - return; - } - - QDir saveDir(savePath); - if (!saveDir.exists()) { - if (saveDir.mkpath(saveDir.absolutePath())) { - showMessage(tr("Unable to create the directory:\n").append(saveDir.absolutePath()), MessageWidget::Error); - return; - } - } - config()->set("LastAttachmentDir", QFileInfo(saveDir.absolutePath()).absolutePath()); - - QStringList errors; - for (const QModelIndex &index: indexes) { - const QString filename = m_attachmentsModel->keyByIndex(index); - const QString attachmentPath = saveDir.absoluteFilePath(filename); - - if (QFileInfo::exists(attachmentPath)) { - const QString question(tr("Are you sure you want to overwrite existing file \"%1\" with the attachment?")); - auto ans = MessageBox::question(this, tr("Confirm overwrite"), question.arg(filename), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); - if (ans == QMessageBox::No) { - continue; - } else if (ans == QMessageBox::Cancel) { - return; - } - } - - QFile file(attachmentPath); - const QByteArray attachmentData = m_entryAttachments->value(filename); - const bool saveOk = file.open(QIODevice::WriteOnly) && file.write(attachmentData) == attachmentData.size(); - if (!saveOk) { - errors.append(QString("%1 - %2").arg(filename, file.errorString())); - } - } - - if (!errors.isEmpty()) { - showMessage(tr("Unable to save the attachments:\n").append(errors.join('\n')), MessageWidget::Error); - } -} - -void EditEntryWidget::openAttachment(const QModelIndex& index) -{ - if (!index.isValid()) { - Q_ASSERT(false); - return; - } - - QString errorMessage; - if (!openAttachment(index, &errorMessage)) { - showMessage(errorMessage, MessageWidget::Error); - } -} - -void EditEntryWidget::openSelectedAttachments() -{ - const QModelIndexList indexes = m_advancedUi->attachmentsView->selectionModel()->selectedRows(0); - if (indexes.isEmpty()) { - return; - } - - QStringList errors; - for (const QModelIndex &index: indexes) { - QString errorMessage; - if (!openAttachment(index, &errorMessage)) { - const QString filename = m_attachmentsModel->keyByIndex(index); - errors.append(QString("%1 - %2").arg(filename, errorMessage)); - }; - } - - if (!errors.isEmpty()) { - showMessage(tr("Unable to open the attachments:\n").append(errors.join('\n')), MessageWidget::Error); - } -} - -void EditEntryWidget::removeSelectedAttachments() -{ - Q_ASSERT(!m_history); - - const QModelIndexList indexes = m_advancedUi->attachmentsView->selectionModel()->selectedRows(0); - if (indexes.isEmpty()) { - return; - } - - const QString question = tr("Are you sure you want to remove %n attachments?", "", indexes.count()); - QMessageBox::StandardButton ans = MessageBox::question(this, tr("Confirm Remove"), - question, QMessageBox::Yes | QMessageBox::No); - if (ans == QMessageBox::Yes) { - QStringList keys; - for (const QModelIndex &index: indexes) { - keys.append(m_attachmentsModel->keyByIndex(index)); - } - m_entryAttachments->remove(keys); - } -} - -void EditEntryWidget::updateAttachmentButtonsEnabled() -{ - const bool hasSelection = m_advancedUi->attachmentsView->selectionModel()->hasSelection(); - - m_advancedUi->saveAttachmentButton->setEnabled(hasSelection); - m_advancedUi->openAttachmentButton->setEnabled(hasSelection); - m_advancedUi->removeAttachmentButton->setEnabled(hasSelection && !m_history); -} - void EditEntryWidget::updateAutoTypeEnabled() { bool autoTypeEnabled = m_autoTypeUi->enableButton->isChecked(); diff --git a/src/gui/entry/EditEntryWidget.h b/src/gui/entry/EditEntryWidget.h index d40b5c14..b23f7b65 100644 --- a/src/gui/entry/EditEntryWidget.h +++ b/src/gui/entry/EditEntryWidget.h @@ -31,8 +31,6 @@ class Database; class EditWidgetIcons; class EditWidgetProperties; class Entry; -class EntryAttachments; -class EntryAttachmentsModel; class EntryAttributes; class EntryAttributesModel; class EntryHistoryModel; @@ -86,14 +84,6 @@ private slots: void updateCurrentAttribute(); void protectCurrentAttribute(bool state); void revealCurrentAttribute(); - void insertAttachments(const QStringList& fileNames, QString* errorMessage = nullptr); - void insertAttachments(); - void saveSelectedAttachment(); - void saveSelectedAttachments(); - void openAttachment(const QModelIndex& index); - void openSelectedAttachments(); - void removeSelectedAttachments(); - void updateAttachmentButtonsEnabled(); void updateAutoTypeEnabled(); void insertAutoTypeAssoc(); void removeAutoTypeAssoc(); @@ -141,10 +131,6 @@ private: void displayAttribute(QModelIndex index, bool showProtected); - bool openAttachment(const QModelIndex& index, QString *errorMessage); - - bool eventFilter(QObject* watched, QEvent* event) override; - Entry* m_entry; Database* m_database; @@ -167,8 +153,6 @@ private: QWidget* const m_sshAgentWidget; EditWidgetProperties* const m_editWidgetProperties; QWidget* const m_historyWidget; - EntryAttachments* const m_entryAttachments; - EntryAttachmentsModel* const m_attachmentsModel; EntryAttributes* const m_entryAttributes; EntryAttributesModel* const m_attributesModel; EntryHistoryModel* const m_historyModel; diff --git a/src/gui/entry/EditEntryWidgetAdvanced.ui b/src/gui/entry/EditEntryWidgetAdvanced.ui index 5085d164..8c729fd7 100644 --- a/src/gui/entry/EditEntryWidgetAdvanced.ui +++ b/src/gui/entry/EditEntryWidgetAdvanced.ui @@ -2,14 +2,6 @@ EditEntryWidgetAdvanced - - - 0 - 0 - 400 - 366 - - 0 @@ -153,61 +145,14 @@ - - - - - - - - Add - - - - - - - false - - - Remove - - - - - - - false - - - Open - - - - - - - false - - - Save - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - + + + + 100 + 100 + + + @@ -215,6 +160,12 @@ + + EntryAttachmentsWidget + QWidget +
gui/entry/EntryAttachmentsWidget.h
+ 1 +
AttributesListView QListView @@ -227,10 +178,6 @@ addAttributeButton removeAttributeButton editAttributeButton - addAttachmentButton - removeAttachmentButton - openAttachmentButton - saveAttachmentButton diff --git a/src/gui/entry/EntryAttachmentsWidget.cpp b/src/gui/entry/EntryAttachmentsWidget.cpp new file mode 100644 index 00000000..93b73a16 --- /dev/null +++ b/src/gui/entry/EntryAttachmentsWidget.cpp @@ -0,0 +1,353 @@ +#include "EntryAttachmentsWidget.h" +#include "ui_EntryAttachmentsWidget.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "EntryAttachmentsModel.h" +#include "core/Config.h" +#include "core/EntryAttachments.h" +#include "core/Tools.h" +#include "gui/FileDialog.h" +#include "gui/MessageBox.h" + + +EntryAttachmentsWidget::EntryAttachmentsWidget(QWidget* parent) : + QWidget(parent) + , m_ui(new Ui::EntryAttachmentsWidget) + , m_entryAttachments(new EntryAttachments(this)) + , m_attachmentsModel(new EntryAttachmentsModel(this)) + , m_readOnly(false) + , m_buttonsVisible(true) +{ + m_ui->setupUi(this); + + m_ui->attachmentsView->setAcceptDrops(false); + m_ui->attachmentsView->viewport()->setAcceptDrops(true); + m_ui->attachmentsView->viewport()->installEventFilter(this); + + m_attachmentsModel->setEntryAttachments(m_entryAttachments); + m_ui->attachmentsView->setModel(m_attachmentsModel); + m_ui->attachmentsView->verticalHeader()->hide(); + m_ui->attachmentsView->horizontalHeader()->setStretchLastSection(true); + m_ui->attachmentsView->horizontalHeader()->resizeSection(EntryAttachmentsModel::NameColumn, 400); + m_ui->attachmentsView->setSelectionBehavior(QAbstractItemView::SelectRows); + m_ui->attachmentsView->setSelectionMode(QAbstractItemView::ExtendedSelection); + + m_ui->actionsWidget->setVisible(m_buttonsVisible); + connect(this, SIGNAL(buttonsVisibleChanged(bool)), m_ui->actionsWidget, SLOT(setVisible(bool))); + + connect(this, SIGNAL(readOnlyChanged(bool)), SLOT(updateButtonsEnabled())); + connect(m_attachmentsModel, SIGNAL(modelReset()), SLOT(updateButtonsEnabled())); + connect(m_ui->attachmentsView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + SLOT(updateButtonsEnabled())); + + connect(m_ui->attachmentsView, SIGNAL(doubleClicked(QModelIndex)), SLOT(openAttachment(QModelIndex))); + connect(m_ui->saveAttachmentButton, SIGNAL(clicked()), SLOT(saveSelectedAttachments())); + connect(m_ui->openAttachmentButton, SIGNAL(clicked()), SLOT(openSelectedAttachments())); + connect(m_ui->addAttachmentButton, SIGNAL(clicked()), SLOT(insertAttachments())); + connect(m_ui->removeAttachmentButton, SIGNAL(clicked()), SLOT(removeSelectedAttachments())); + + updateButtonsEnabled(); +} + +EntryAttachmentsWidget::~EntryAttachmentsWidget() +{ +} + +const EntryAttachments* EntryAttachmentsWidget::entryAttachments() const +{ + return m_entryAttachments; +} + +bool EntryAttachmentsWidget::isReadOnly() const +{ + return m_readOnly; +} + +bool EntryAttachmentsWidget::isButtonsVisible() const +{ + return m_buttonsVisible; +} + +void EntryAttachmentsWidget::setEntryAttachments(const EntryAttachments* attachments) +{ + Q_ASSERT(attachments != nullptr); + m_entryAttachments->copyDataFrom(attachments); +} + +void EntryAttachmentsWidget::clearAttachments() +{ + m_entryAttachments->clear(); +} + +void EntryAttachmentsWidget::setReadOnly(bool readOnly) +{ + if (m_readOnly == readOnly) { + return; + } + + m_readOnly = readOnly; + emit readOnlyChanged(m_readOnly); +} + +void EntryAttachmentsWidget::setButtonsVisible(bool buttonsVisible) +{ + if (m_buttonsVisible == buttonsVisible) { + return; + } + + m_buttonsVisible = buttonsVisible; + emit buttonsVisibleChanged(m_buttonsVisible); +} + +void EntryAttachmentsWidget::insertAttachments() +{ + Q_ASSERT(!isReadOnly()); + if (isReadOnly()) { + return; + } + + QString defaultDirPath = config()->get("LastAttachmentDir").toString(); + const bool dirExists = !defaultDirPath.isEmpty() && QDir(defaultDirPath).exists(); + if (!dirExists) { + defaultDirPath = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation).first(); + } + + const QStringList filenames = fileDialog()->getOpenFileNames(this, tr("Select files"), defaultDirPath); + if (filenames.isEmpty()) { + return; + } + + config()->set("LastAttachmentDir", QFileInfo(filenames.first()).absolutePath()); + + QString errorMessage; + if (!insertAttachments(filenames, errorMessage)) { + errorOccurred(errorMessage); + } +} + +void EntryAttachmentsWidget::removeSelectedAttachments() +{ + Q_ASSERT(!isReadOnly()); + if (isReadOnly()) { + return; + } + + const QModelIndexList indexes = m_ui->attachmentsView->selectionModel()->selectedRows(0); + if (indexes.isEmpty()) { + return; + } + + const QString question = tr("Are you sure you want to remove %n attachment(s)?", "", indexes.count()); + QMessageBox::StandardButton answer = MessageBox::question(this, tr("Confirm Remove"), + question, QMessageBox::Yes | QMessageBox::No); + if (answer == QMessageBox::Yes) { + QStringList keys; + for (const QModelIndex& index: indexes) { + keys.append(m_attachmentsModel->keyByIndex(index)); + } + m_entryAttachments->remove(keys); + } +} + +void EntryAttachmentsWidget::saveSelectedAttachments() +{ + const QModelIndexList indexes = m_ui->attachmentsView->selectionModel()->selectedRows(0); + if (indexes.isEmpty()) { + return; + } + + QString defaultDirPath = config()->get("LastAttachmentDir").toString(); + const bool dirExists = !defaultDirPath.isEmpty() && QDir(defaultDirPath).exists(); + if (!dirExists) { + defaultDirPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + } + + const QString saveDirPath = fileDialog()->getExistingDirectory(this, tr("Save attachments"), defaultDirPath); + if (saveDirPath.isEmpty()) { + return; + } + + QDir saveDir(saveDirPath); + if (!saveDir.exists()) { + if (saveDir.mkpath(saveDir.absolutePath())) { + errorOccurred(tr("Unable to create directory:\n%1").arg(saveDir.absolutePath())); + return; + } + } + config()->set("LastAttachmentDir", QFileInfo(saveDir.absolutePath()).absolutePath()); + + QStringList errors; + for (const QModelIndex& index: indexes) { + const QString filename = m_attachmentsModel->keyByIndex(index); + const QString attachmentPath = saveDir.absoluteFilePath(filename); + + if (QFileInfo::exists(attachmentPath)) { + const QString question(tr("Are you sure you want to overwrite the existing file \"%1\" with the attachment?")); + auto answer = MessageBox::question(this, tr("Confirm overwrite"), question.arg(filename), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + if (answer == QMessageBox::No) { + continue; + } else if (answer == QMessageBox::Cancel) { + return; + } + } + + QFile file(attachmentPath); + const QByteArray attachmentData = m_entryAttachments->value(filename); + const bool saveOk = file.open(QIODevice::WriteOnly) && file.write(attachmentData) == attachmentData.size(); + if (!saveOk) { + errors.append(QString("%1 - %2").arg(filename, file.errorString())); + } + } + + if (!errors.isEmpty()) { + errorOccurred(tr("Unable to save attachments:\n%1").arg(errors.join('\n'))); + } +} + +void EntryAttachmentsWidget::openAttachment(const QModelIndex& index) +{ + Q_ASSERT(index.isValid()); + if (!index.isValid()) { + return; + } + + QString errorMessage; + if (!openAttachment(index, errorMessage)) { + errorOccurred(tr("Unable to open attachment:\n%1").arg(errorMessage)); + } +} + +void EntryAttachmentsWidget::openSelectedAttachments() +{ + const QModelIndexList indexes = m_ui->attachmentsView->selectionModel()->selectedRows(0); + if (indexes.isEmpty()) { + return; + } + + QStringList errors; + for (const QModelIndex& index: indexes) { + QString errorMessage; + if (!openAttachment(index, errorMessage)) { + const QString filename = m_attachmentsModel->keyByIndex(index); + errors.append(QString("%1 - %2").arg(filename, errorMessage)); + }; + } + + if (!errors.isEmpty()) { + errorOccurred(tr("Unable to open attachments:\n%1").arg(errors.join('\n'))); + } +} + +void EntryAttachmentsWidget::updateButtonsEnabled() +{ + const bool hasSelection = m_ui->attachmentsView->selectionModel()->hasSelection(); + + m_ui->addAttachmentButton->setEnabled(!m_readOnly); + m_ui->removeAttachmentButton->setEnabled(hasSelection && !m_readOnly); + + m_ui->saveAttachmentButton->setEnabled(hasSelection); + m_ui->openAttachmentButton->setEnabled(hasSelection); +} + +bool EntryAttachmentsWidget::insertAttachments(const QStringList& filenames, QString& errorMessage) +{ + Q_ASSERT(!isReadOnly()); + if (isReadOnly()) { + return false; + } + + QStringList errors; + for (const QString &filename: filenames) { + QByteArray data; + QFile file(filename); + const QFileInfo fInfo(filename); + const bool readOk = file.open(QIODevice::ReadOnly) && Tools::readAllFromDevice(&file, data); + if (readOk) { + m_entryAttachments->set(fInfo.fileName(), data); + } else { + errors.append(QString("%1 - %2").arg(fInfo.fileName(), file.errorString())); + } + } + + if (!errors.isEmpty()) { + errorMessage = tr("Unable to open files:\n%1").arg(errors.join('\n')); + } + + return errors.isEmpty(); +} + +bool EntryAttachmentsWidget::openAttachment(const QModelIndex& index, QString& errorMessage) +{ + const QString filename = m_attachmentsModel->keyByIndex(index); + const QByteArray attachmentData = m_entryAttachments->value(filename); + + // tmp file will be removed once the database (or the application) has been closed + const QString tmpFileTemplate = QDir::temp().absoluteFilePath(QString("XXXXXX.").append(filename)); + + QScopedPointer tmpFile(new QTemporaryFile(tmpFileTemplate, this)); + + const bool saveOk = tmpFile->open() + && tmpFile->write(attachmentData) == attachmentData.size() + && tmpFile->flush(); + if (!saveOk) { + errorMessage = QString("%1 - %2").arg(filename, tmpFile->errorString()); + return false; + } + + tmpFile->close(); + const bool openOk = QDesktopServices::openUrl(QUrl::fromLocalFile(tmpFile->fileName())); + if (!openOk) { + errorMessage = QString("Can't open file \"%1\"").arg(filename); + return false; + } + + // take ownership of the tmpFile pointer + tmpFile.take(); + return true; +} + +bool EntryAttachmentsWidget::eventFilter(QObject* watched, QEvent* e) +{ + if (watched == m_ui->attachmentsView->viewport() && !isReadOnly()) { + const QEvent::Type eventType = e->type(); + if (eventType == QEvent::DragEnter || eventType == QEvent::DragMove) { + QDropEvent* dropEv = static_cast(e); + const QMimeData* mimeData = dropEv->mimeData(); + if (mimeData->hasUrls()) { + dropEv->acceptProposedAction(); + return true; + } + } else if (eventType == QEvent::Drop) { + QDropEvent* dropEv = static_cast(e); + const QMimeData* mimeData = dropEv->mimeData(); + if (mimeData->hasUrls()) { + dropEv->acceptProposedAction(); + QStringList filenames; + const QList urls = mimeData->urls(); + for (const QUrl& url: urls) { + const QFileInfo fInfo(url.toLocalFile()); + if (fInfo.isFile()) { + filenames.append(fInfo.absoluteFilePath()); + } + } + + QString errorMessage; + if (!insertAttachments(filenames, errorMessage)) { + errorOccurred(errorMessage); + } + + return true; + } + } + } + + return QWidget::eventFilter(watched, e); +} diff --git a/src/gui/entry/EntryAttachmentsWidget.h b/src/gui/entry/EntryAttachmentsWidget.h new file mode 100644 index 00000000..41a54d47 --- /dev/null +++ b/src/gui/entry/EntryAttachmentsWidget.h @@ -0,0 +1,59 @@ +#ifndef ENTRYATTACHMENTSWIDGET_H +#define ENTRYATTACHMENTSWIDGET_H + +#include +#include + +namespace Ui { +class EntryAttachmentsWidget; +} + +class EntryAttachments; +class EntryAttachmentsModel; + +class EntryAttachmentsWidget : public QWidget +{ + Q_OBJECT + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly NOTIFY readOnlyChanged) + Q_PROPERTY(bool isButtonsVisible READ isButtonsVisible WRITE setButtonsVisible NOTIFY buttonsVisibleChanged) +public: + explicit EntryAttachmentsWidget(QWidget* parent = nullptr); + ~EntryAttachmentsWidget(); + + const EntryAttachments* entryAttachments() const; + bool isReadOnly() const; + bool isButtonsVisible() const; + +public slots: + void setEntryAttachments(const EntryAttachments* attachments); + void clearAttachments(); + void setReadOnly(bool readOnly); + void setButtonsVisible(bool isButtonsVisible); + +signals: + void errorOccurred(const QString& error); + void readOnlyChanged(bool readOnly); + void buttonsVisibleChanged(bool isButtonsVisible); + +private slots: + void insertAttachments(); + void removeSelectedAttachments(); + void saveSelectedAttachments(); + void openAttachment(const QModelIndex& index); + void openSelectedAttachments(); + void updateButtonsEnabled(); + +private: + bool insertAttachments(const QStringList& fileNames, QString& errorMessage); + bool openAttachment(const QModelIndex& index, QString& errorMessage); + + bool eventFilter(QObject* watched, QEvent* event) override; + + QScopedPointer m_ui; + QPointer m_entryAttachments; + QPointer m_attachmentsModel; + bool m_readOnly; + bool m_buttonsVisible; +}; + +#endif // ENTRYATTACHMENTSWIDGET_H diff --git a/src/gui/entry/EntryAttachmentsWidget.ui b/src/gui/entry/EntryAttachmentsWidget.ui new file mode 100644 index 00000000..60292309 --- /dev/null +++ b/src/gui/entry/EntryAttachmentsWidget.ui @@ -0,0 +1,99 @@ + + + EntryAttachmentsWidget + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + Add + + + + + + + false + + + Remove + + + + + + + false + + + Open + + + + + + + false + + + Save + + + + + + + Qt::Vertical + + + + 20 + 173 + + + + + + + + + + + +